Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
41206526e1
|
|
@ -1,9 +1,9 @@
|
||||||
package day01.scala
|
package day01.scala
|
||||||
|
|
||||||
//import org.apache.flink.api.common.functions.FlatMapFunction
|
import org.apache.flink.api.common.functions.FlatMapFunction
|
||||||
//import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator
|
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator
|
||||||
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment
|
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment
|
||||||
//import org.apache.flink.util.Collector
|
import org.apache.flink.util.Collector
|
||||||
|
|
||||||
object Example_scala1 {
|
object Example_scala1 {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,35 +58,35 @@ public class Example2 {
|
||||||
.print();
|
.print();
|
||||||
|
|
||||||
//匿名内部类的方式
|
//匿名内部类的方式
|
||||||
// env
|
env
|
||||||
// .addSource(new SourceFunction<Integer>() {
|
.addSource(new SourceFunction<Integer>() {
|
||||||
//
|
|
||||||
// private boolean running = true;
|
private boolean running = true;
|
||||||
// private Random random = new Random();
|
private Random random = new Random();
|
||||||
//
|
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public void run(SourceContext<Integer> ctx) throws Exception {
|
public void run(SourceContext<Integer> ctx) throws Exception {
|
||||||
//
|
|
||||||
// while (running) {
|
while (running) {
|
||||||
// ctx.collect(random.nextInt(1000));
|
ctx.collect(random.nextInt(1000));
|
||||||
// Thread.sleep(1000L);
|
Thread.sleep(1000L);
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// @Override
|
@Override
|
||||||
// public void cancel() {
|
public void cancel() {
|
||||||
// running = false;
|
running = false;
|
||||||
//
|
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
// .map(new MapFunction<Integer, Tuple2<Integer, Integer>>() {
|
.map(new MapFunction<Integer, Tuple2<Integer, Integer>>() {
|
||||||
// @Override
|
@Override
|
||||||
// public Tuple2<Integer, Integer> map(Integer value) throws Exception {
|
public Tuple2<Integer, Integer> map(Integer value) throws Exception {
|
||||||
// return Tuple2.of(value, value);
|
return Tuple2.of(value, value);
|
||||||
// }
|
}
|
||||||
// }).print();
|
}).print();
|
||||||
|
|
||||||
|
|
||||||
//外部类的方式
|
//外部类的方式
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
|
||||||
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
|
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
|
||||||
import org.apache.flink.streaming.api.functions.source.SourceFunction;
|
import org.apache.flink.streaming.api.functions.source.SourceFunction;
|
||||||
import org.apache.flink.util.Collector;
|
import org.apache.flink.util.Collector;
|
||||||
import sun.awt.SunHints;
|
|
||||||
|
|
||||||
import java.sql.Time;
|
import java.sql.Time;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public class Example7 {
|
||||||
env.setParallelism(1);
|
env.setParallelism(1);
|
||||||
|
|
||||||
env
|
env
|
||||||
.readTextFile("D:\\example\\self_example\\Big_data_example\\Flink\\src\\main\\resources\\UserBehavior.csv")
|
.readTextFile("E:\\self_example\\Big_data_example\\Flink\\src\\main\\resources\\UserBehavior.csv")
|
||||||
.map(new MapFunction<String, UserBehavior>() {
|
.map(new MapFunction<String, UserBehavior>() {
|
||||||
@Override
|
@Override
|
||||||
public UserBehavior map(String value) throws Exception {
|
public UserBehavior map(String value) throws Exception {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ public class Example4 {
|
||||||
@Override
|
@Override
|
||||||
public void process(String s, Context context, Iterable<Tuple2<String, Long>> iterable, Collector<String> collector) throws Exception {
|
public void process(String s, Context context, Iterable<Tuple2<String, Long>> iterable, Collector<String> collector) throws Exception {
|
||||||
|
|
||||||
//初始化一个窗口状态变量,注意:窗口状态变量的可见范围是当前窗口
|
//初始化一个窗口状态变量,注意:窗口状态变量的可见范围是当前窗口(无论窗口是否被销毁)
|
||||||
ValueState<Boolean> firstCalculate = context.windowState().getState(new ValueStateDescriptor<Boolean>("first", Types.BOOLEAN));
|
ValueState<Boolean> firstCalculate = context.windowState().getState(new ValueStateDescriptor<Boolean>("first", Types.BOOLEAN));
|
||||||
|
|
||||||
if (firstCalculate.value() == null) {
|
if (firstCalculate.value() == null) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
|
||||||
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
|
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
|
||||||
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
|
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
|
||||||
import org.apache.flink.util.Collector;
|
import org.apache.flink.util.Collector;
|
||||||
import sun.awt.geom.AreaOp;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,6 @@ public class Example9 {
|
||||||
+I[Bob, 2, 1970-01-01T15:00]
|
+I[Bob, 2, 1970-01-01T15:00]
|
||||||
+I[Mary, 1, 1970-01-01T15:00]
|
+I[Mary, 1, 1970-01-01T15:00]
|
||||||
+I[liz, 1, 1970-01-01T15:00]
|
+I[liz, 1, 1970-01-01T15:00]
|
||||||
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* kafka消费者
|
* kafka消费者:自动提交offset
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import java.util.Map;
|
||||||
public class TimeStampInterceptor implements ProducerInterceptor<String,String> {
|
public class TimeStampInterceptor implements ProducerInterceptor<String,String> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拦截器的黑心处理方法
|
* 拦截器的核心处理方法
|
||||||
* @param record 被拦截处理的消息
|
* @param record 被拦截处理的消息
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ public class T27_LengthOfLIS {
|
||||||
r = mid - 1;
|
r = mid - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d[pos + 1] = nums[i];
|
d[pos + 1] = nums[i];//把第一个小于的位置替换成这个数//并不影响原本的长度,只有后续比他更长了,才会影响他的长度
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len;
|
return len;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-19 13:41
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣70 爬楼梯(进阶版):
|
||||||
|
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
|
||||||
|
* 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T14_ClimbStairs {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:可以看做是一个完全背包问题: 背包大小是n,nums为[1,2];
|
||||||
|
* 由于爬楼梯先爬1后爬2和先爬2后爬1是不一样的结果;所以遍历nums需要放里面
|
||||||
|
* 速度击败100% 内存击败27.4%
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int climbStairs(int n) {
|
||||||
|
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
dp[0] = 1;
|
||||||
|
|
||||||
|
|
||||||
|
for (int j = 0; j < dp.length; j++) {
|
||||||
|
for (int i = 1; i <= 2; i++) {//可以走1或者2
|
||||||
|
if (j >= i) dp[j] = dp[j - i] + dp[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[n];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-19 13:58
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣322题 零钱兑换:
|
||||||
|
* 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
|
||||||
|
* 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
|
||||||
|
* 你可以认为每种硬币的数量是无限的。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T15_CoinChange {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] coins = {1, 2, 5};
|
||||||
|
int amount = 11;
|
||||||
|
System.out.println(coinChange(coins, amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] coins = {2};
|
||||||
|
int amount = 3;
|
||||||
|
System.out.println(coinChange1(coins, amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
int[] coins = {2, 5, 10, 1};
|
||||||
|
int amount = 27;
|
||||||
|
System.out.println(coinChange(coins, amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test3() {
|
||||||
|
int[] coins = {186,419,83,408};
|
||||||
|
int amount = 6249;
|
||||||
|
System.out.println(coinChange(coins, amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:从一共有多少种方法解决,变成了这些方法中最少可以用多少种:
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示使用nums[0-i]凑出j的使用最少货币数
|
||||||
|
* 2.dp状态转移方程: dp[i][j]可以选择 再多使用当前nums[i]一次 或者 不适用当前nums[i]
|
||||||
|
* dp[i][j]=min(dp[i][j-nums[i]]+1,dp[i-1][j])
|
||||||
|
* 3.dp初始化:dp[i][0]=0
|
||||||
|
* 4.dp遍历顺序:求的组合数;所以nums在外
|
||||||
|
* 5.dp举例推导:coins = [1, 2, 5], amount = 11
|
||||||
|
* [0 1 2 3 4 5 6 7 8 9 10 11]
|
||||||
|
* i=1: 0 1 2 3 4 5 6 7 8 9 10 11
|
||||||
|
* i=2: 0 1 1 2 2 3 3 4 4 5 5 6
|
||||||
|
* i=5: 0 1 1 2 2 1 2 2 3 3 2 3
|
||||||
|
* 速度击败23.5%
|
||||||
|
* @param coins
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int coinChange(int[] coins, int amount) {
|
||||||
|
|
||||||
|
int[][] dp = new int[coins.length][amount + 1];
|
||||||
|
Arrays.sort(coins);
|
||||||
|
for (int[] d : dp) {
|
||||||
|
Arrays.fill(d, Integer.MAX_VALUE); //注意这里必须是Max_Value,不能是Min_Value,否则Min可能取不到
|
||||||
|
}
|
||||||
|
|
||||||
|
dp[0][0] = 0;
|
||||||
|
for (int i = 1; i < dp[0].length; i++) {
|
||||||
|
if (i >= coins[0] && dp[0][i - coins[0]] != Integer.MAX_VALUE) dp[0][i] = dp[0][i - coins[0]] + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < coins.length; i++) {
|
||||||
|
for (int j = 0; j < dp[0].length; j++) {
|
||||||
|
if (j >= coins[i] && dp[i][j - coins[i]] != Integer.MAX_VALUE)
|
||||||
|
dp[i][j] = Math.min(dp[i][j - coins[i]] + 1, dp[i - 1][j]);
|
||||||
|
else dp[i][j] = dp[i - 1][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[coins.length - 1][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length - 1][amount];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* @param coins
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int coinChange1(int[] coins, int amount) {
|
||||||
|
|
||||||
|
int[] dp = new int[amount + 1];
|
||||||
|
Arrays.fill(dp,Integer.MAX_VALUE);
|
||||||
|
dp[0]=0;
|
||||||
|
|
||||||
|
for (int i = 0; i < coins.length; i++) {
|
||||||
|
for (int j = coins[i]; j < dp.length; j++) {
|
||||||
|
if (dp[j-coins[i]]!=Integer.MAX_VALUE)
|
||||||
|
dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-19 14:55
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣279 完全平方数:
|
||||||
|
* 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
|
||||||
|
* 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T16_NumSquares {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// int t = 10;
|
||||||
|
// System.out.println(Math.sqrt(t));
|
||||||
|
// System.out.println((int) Math.sqrt(t));
|
||||||
|
// System.out.println((int) Math.sqrt(9) == Math.sqrt(9));
|
||||||
|
System.out.println(numSquares(12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:与上一个零钱问题类似,事实上零钱问题给出了可用的零钱数;而这一题需要判断是否是可用的零钱,
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义:dp[j]表示使用完全平方数能凑出的最少个数
|
||||||
|
* 2.dp状态转移方程:dp[j]=min(dp[j],dp[j-完全])
|
||||||
|
* 3.dp初始化:dp[0]=0;
|
||||||
|
* 4.dp遍历顺序:都可以
|
||||||
|
* 5.dp状态转移方程: n=12:
|
||||||
|
* [0 1 2 3 4 5 6 7 8 9 10 11 12]
|
||||||
|
* i=1: 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||||
|
* i=4: 0 1 2 3 1 2 3 4 2 3 4 5 3
|
||||||
|
* i=9: 0 1 2 3 1 2 3 4 2 1 2 3 3
|
||||||
|
* 速度击败9.4% 内存击败37.54% 157ms
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numSquares(int n) {
|
||||||
|
List<Integer> list = findSquares(n);
|
||||||
|
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
Arrays.fill(dp, Integer.MAX_VALUE);
|
||||||
|
dp[0] = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
for (int j = list.get(i); j < dp.length; j++) {
|
||||||
|
if (dp[j - list.get(i)] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[j - list.get(i)] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[n];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> findSquares(int n) {
|
||||||
|
List<Integer> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 1; i <= n; i++) {
|
||||||
|
if ((int) Math.sqrt(i) == Math.sqrt(i)) {
|
||||||
|
list.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方一维dp,避免判断完全平方数
|
||||||
|
* 速度击败65.5% 内存击败44.71%
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numSquares1(int n) {
|
||||||
|
int max = n + 1; //因为最少可以用1凑n,最多只需要n个数字
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
dp[0] = 0;
|
||||||
|
for (int i = 1; i < max; i++) {
|
||||||
|
dp[i] = max;
|
||||||
|
}
|
||||||
|
int maxSqrt = (int) Math.sqrt(n);
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxSqrt; i++) {
|
||||||
|
for (int j = i * i; j < n + 1; j++) {
|
||||||
|
if (dp[j - i * i] != max) {
|
||||||
|
dp[j] = Math.min(dp[j], dp[j - i * i] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-20 09:40
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣139 单词拆分:
|
||||||
|
* 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
|
||||||
|
* 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T17_WordBreak {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
String s = "leetcode";
|
||||||
|
List<String> wordDict = new ArrayList<>(Arrays.asList("leet", "code"));
|
||||||
|
System.out.println(wordBreak(s,wordDict));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 难点:查分之后如何去和wordDict中的单词进行匹配;如果匹配应该从何处进行匹配?
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示使用words[0-i]能否匹配上s[0-j]
|
||||||
|
* 2.dp状态转移方程: dp[i][j] :使用当前这个word或者不使用当前这个word
|
||||||
|
* dp[i][j]=dp[i-1][j] || dp[i][j-word[i].length]&&s[j-word[i].length - j]==words[i]
|
||||||
|
* 3.dp初始化: dp[i][0]=true
|
||||||
|
* 4.dp遍历顺序: 先遍历s再遍历words 因为words里面的单词可以重复使用
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败71.9% 内存击败74.85% 6ms
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak(String s, List<String> wordDict) {
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
dp[0] = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
for (int j = 0; j < wordDict.size(); j++) {
|
||||||
|
int length = wordDict.get(j).length();
|
||||||
|
if (i >= length) dp[i] |= (dp[i - length] && isEqual(i, length, s, wordDict.get(j)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length()];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEqual(int j, int length, String s, String wordDict) {
|
||||||
|
|
||||||
|
return wordDict.equals(s.substring(j - length, j));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方一维dp优化: 1.for循环优化;2.等于true不需要继续遍历words往下判断了
|
||||||
|
* 速度击败93.3% 内存击败92.28% 2ms
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak1(String s, List<String> wordDict) {
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
dp[0] = true;
|
||||||
|
|
||||||
|
for (int i = 1; i <= s.length(); i++) {
|
||||||
|
for (String word : wordDict) {
|
||||||
|
int len = word.length();
|
||||||
|
if (i >= len && dp[i - len] && word.equals(s.substring(i - len, i))) {
|
||||||
|
dp[i] = true;//
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s.length()];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:回溯+记忆化搜索
|
||||||
|
* 速度击败100% 内存击败99.56%
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak3(String s, List<String> wordDict) {
|
||||||
|
int[] memo = new int[s.length()];
|
||||||
|
Arrays.fill(memo, -1);
|
||||||
|
return check(s, 0, wordDict, memo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean check(String s, int idx, List<String> wordDict, int[] memo) {
|
||||||
|
if (idx == s.length()) return true;
|
||||||
|
if (memo[idx] != -1) return memo[idx] != 0;//记忆化回溯
|
||||||
|
for (String word : wordDict)
|
||||||
|
if (s.startsWith(word, idx) && check(s, idx + word.length(), wordDict, memo)) {
|
||||||
|
memo[idx] = 1;//找到了
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//没找到
|
||||||
|
memo[idx] = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-20 10:39
|
||||||
|
*@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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 打家劫舍问题: 本质上可以分为 : 偷不偷当前,在偷和不偷之间找最优
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示不偷i的最大值 ;dp[i][1]表示偷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]=dp[i-1][0]+nums[i]
|
||||||
|
* 3.dp初始化:dp[0][0]=0; dp[0][1]=nums[0]
|
||||||
|
* 4.dp遍历顺序:从前往后
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob(int[] nums) {
|
||||||
|
|
||||||
|
int[][] dp = new int[nums.length][2];
|
||||||
|
dp[0][0]=0; dp[0][1]=nums[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
|
||||||
|
dp[i][1]=dp[i-1][0]+nums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp[dp.length-1][0],dp[dp.length-1][1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动数组优化
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob1(int[] nums) {
|
||||||
|
|
||||||
|
|
||||||
|
int dp0=0; int dp1=nums[0];
|
||||||
|
int temp=0;
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
temp=dp0;
|
||||||
|
dp0=Math.max(dp0,dp1);
|
||||||
|
dp1=temp+nums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp0,dp1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-20 10:57
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣213题 打家劫舍II:
|
||||||
|
* 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。
|
||||||
|
* 这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。
|
||||||
|
* 同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
|
||||||
|
* 给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T19_Rob {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] nums={2,3,2};
|
||||||
|
System.out.println(rob(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:与T18的不同之处在于:是环形的,收尾相连 :所以分别考虑只要第一个的时候,和只要最后一个的时候;结果出现在这两个之中最大的那个
|
||||||
|
* TODO DP五部曲: 与之前一致,只不过一个只考虑不要最后一个;一个只考虑不要第一个
|
||||||
|
* 速度击败2.92% 内存击败48.19%
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob(int[] nums) {
|
||||||
|
if (nums.length == 1) {
|
||||||
|
return nums[0];//只有一个的情况直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
int[][] dp = new int[nums.length][2];//考虑不要最后一个的情况
|
||||||
|
int[][] dp1 = new int[nums.length][2];//考虑不要第一个的情况
|
||||||
|
dp[0][0] = 0;
|
||||||
|
dp[0][1] = nums[0];
|
||||||
|
dp1[1][0] = 0;
|
||||||
|
dp1[1][1] = nums[1];
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length-1; i++) {
|
||||||
|
//不考虑最后一个
|
||||||
|
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
|
||||||
|
dp[i][1] = dp[i - 1][0] + nums[i];
|
||||||
|
|
||||||
|
|
||||||
|
//不考虑第一个
|
||||||
|
dp1[i + 1][0] = Math.max(dp1[i][0], dp1[i][1]);
|
||||||
|
dp1[i + 1][1] = dp1[i][0] + nums[i + 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
int max= Math.max(dp[nums.length - 2][0],dp[nums.length - 2][1]);
|
||||||
|
int max1=Math.max(dp1[nums.length -1][0],dp1[nums.length -1][1]);
|
||||||
|
|
||||||
|
|
||||||
|
return Math.max(max, max1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob1(int[] nums) {
|
||||||
|
if (nums.length == 1) {
|
||||||
|
return nums[0];//只有一个的情况直接返回
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int dp0 = 0;//考虑不要最后一个的情况
|
||||||
|
int dp1 = nums[0];
|
||||||
|
int temp=0;
|
||||||
|
int dp10 = 0;//考虑不要第一个的情况
|
||||||
|
int dp11 = nums[1];
|
||||||
|
int temp1=0;
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length-1; i++) {
|
||||||
|
//不考虑最后一个
|
||||||
|
temp=dp0;
|
||||||
|
dp0 = Math.max(dp0, dp1);
|
||||||
|
dp1 = temp + nums[i];
|
||||||
|
|
||||||
|
|
||||||
|
//不考虑第一个
|
||||||
|
temp1=dp10;
|
||||||
|
dp10 = Math.max(dp10, dp11);
|
||||||
|
dp11 = temp1 + nums[i + 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
int max= Math.max(dp0,dp1);
|
||||||
|
int max1=Math.max(dp10,dp11);
|
||||||
|
|
||||||
|
return Math.max(max, max1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-20 11:50
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣337 打家劫舍III:
|
||||||
|
* 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
|
||||||
|
* 除了 root 之外,每栋房子有且只有一个“父“房子与之相连。
|
||||||
|
* 一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。
|
||||||
|
* 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
|
||||||
|
* 给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T20_Rob {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 不同之处在于 二叉树地区应该如何处理,所以后续遍历,遍历完子节点之后考虑当前节点是否偷
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示当前节点不偷的最大收益;dp[i][1]表示当前节点偷的最大收益
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][0]不偷:子节点不偷 或者偷的最大 之和
|
||||||
|
* 2.dp[i][1]偷:子节点中不偷之和
|
||||||
|
*
|
||||||
|
* 3.dp初始化:碰到null节点 则 dp[i][0]=0; dp[i][1]=0;
|
||||||
|
* 4.dp遍历顺序:后续遍历,最后在考虑当前节点的问题
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败100% 内存击败76.93%
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob(TreeNode root) {
|
||||||
|
|
||||||
|
int[] root1 = robSub(root);
|
||||||
|
return Math.max(root1[0], root1[1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int[] robSub(TreeNode root) {
|
||||||
|
if (root == null) {
|
||||||
|
return new int[]{0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] left = robSub(root.left);
|
||||||
|
int[] right = robSub(root.right);
|
||||||
|
int noSteal = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
|
||||||
|
int Steal = left[0] + right[0] + root.val;
|
||||||
|
|
||||||
|
return new int[]{noSteal, Steal};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-21 09:50
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣121 买卖股票的最佳时机:
|
||||||
|
* 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
|
||||||
|
* 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
|
||||||
|
* 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T21_MaxProfit {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] prices = {7, 1, 5, 3, 6, 4};
|
||||||
|
System.out.println(maxProfit2(prices));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:事实上思路是一致的:将当前收益分为手上有股票和手上没有股票 注意只能买卖一次
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示第i天手上没有股票的最大收益;dp[i][1]表示第i天手上有股票的最大收益
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][0] 昨天没有今天没买 ; 昨天有今天卖了
|
||||||
|
* dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
|
||||||
|
* 2.dp[i][1] 昨天有今天没卖 ; 昨天没有今天买了
|
||||||
|
* dp[i][1]=max(dp[i-1][1],-prices[i])
|
||||||
|
* 3.dp初始化:dp[0][0]=0; dp[0][1]=-price[0];
|
||||||
|
* 4.dp遍历顺序:从前往后
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败5.69% 内存击败89.6%
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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 < dp.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], -prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[dp.length - 1][0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败54.5% 内存击败66.9%
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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, -prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贪心算法
|
||||||
|
* 速度击败100% 内存击败42.58%
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit2(int[] prices) {
|
||||||
|
|
||||||
|
int minPrice = prices[0];
|
||||||
|
int maxProfit = 0;
|
||||||
|
int curProfit = 0;
|
||||||
|
|
||||||
|
//只买卖一次
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
if (minPrice > prices[i]) {
|
||||||
|
minPrice = prices[i];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
curProfit = prices[i] - minPrice;
|
||||||
|
if (maxProfit < curProfit) maxProfit = curProfit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxProfit;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-21 10:39
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣122 买卖股票的最佳时机II:
|
||||||
|
* 给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
|
||||||
|
* 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
|
||||||
|
* 返回 你能获得的 最大 利润 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T22_MaxProfit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与上一题不同之处在于:收益可以累加
|
||||||
|
* 所以 dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0]-prices[i]);
|
||||||
|
* 速度击败25.56% 内存击败9.38% 3ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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 < dp.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[dp.length - 1][0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败83.44% 内存击败49.3% 1ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贪心
|
||||||
|
* 速度击败83.44% 内存击败81.45% 1ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit2(int[] prices) {
|
||||||
|
|
||||||
|
int profit = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
int curDiff = prices[i] - prices[i - 1];
|
||||||
|
if (curDiff > 0) profit += curDiff;//因为可以当天买当天卖
|
||||||
|
}
|
||||||
|
|
||||||
|
return profit;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-21 10:48
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣123 买卖股票的最佳时机III:
|
||||||
|
* 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
|
||||||
|
* 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
|
||||||
|
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T23_MaxProfit {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:不同之处在于规定了最多只能完成两笔交易 所以将状态定为五种
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义:dp[i][0],dp[i][1],dp[i][2],dp[i][3]分别代表 第i天 第一次买入 第一次卖出 第二次买入 第二次卖出
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][0] 由于是第一次买入,所以之前是没有收益的
|
||||||
|
* dp[i][0]=max(dp[i-1][0],-prices[i])
|
||||||
|
* 2.dp[i][1] 由于是第一次卖出
|
||||||
|
* dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i])
|
||||||
|
* 3.dp[i][2] 由于是第二次买入
|
||||||
|
* dp[i][2]=max(dp[i-1][2],dp[i-1][1]-prices[i])
|
||||||
|
* 4.dp[i][3] 由于是第一次卖出
|
||||||
|
* dp[i][3]=max(dp[i-1][3],dp[i-1][2]+prices[i])
|
||||||
|
* 3.dp初始化:dp[i][0]=-price[0],dp[i][1]=0,dp[i][2]=-price[0],dp[i][3]=0
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导: 以prices = [3,3,5,0,0,3,1,4]为例
|
||||||
|
* [0 1 2 3]
|
||||||
|
* 3: -3 0 -3 0
|
||||||
|
* 3: -3 0 -3 0
|
||||||
|
* 5: -3 2 -3 2
|
||||||
|
* 0: 0 2 2 2
|
||||||
|
* 0: 0 2 2 2
|
||||||
|
* 3: 0 3 2 5
|
||||||
|
* 1: 0 3 2 5
|
||||||
|
* 4: 0 4 2 6
|
||||||
|
* 速度击败23.76% 内存击败80.68% 35ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit(int[] prices) {
|
||||||
|
|
||||||
|
int[][] dp = new int[prices.length][4];
|
||||||
|
dp[0][0] = -prices[0];
|
||||||
|
dp[0][1] = 0;
|
||||||
|
dp[0][2] = -prices[0];
|
||||||
|
dp[0][3] = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
|
||||||
|
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
|
||||||
|
dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]-prices[i]);
|
||||||
|
dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]+prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return dp[dp.length-1][3];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动数组优化
|
||||||
|
* 速度击败87.55% 内存击败42.27% 2ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit1(int[] prices) {
|
||||||
|
|
||||||
|
|
||||||
|
int dp0 = -prices[0];
|
||||||
|
int dp1 = 0;
|
||||||
|
int dp2 = -prices[0];
|
||||||
|
int dp3 = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
dp0=Math.max(dp0,-prices[i]);
|
||||||
|
dp1=Math.max(dp1,dp0+prices[i]);
|
||||||
|
dp2=Math.max(dp2,dp1-prices[i]);
|
||||||
|
dp3=Math.max(dp3,dp2+prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return dp3;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:贪心? 本质上跟上面一样还是动态规划,只是将dp[0]的推导变成了贪心的模式
|
||||||
|
* 速度击败100% 内存击败30.8% 1ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit2(int[] prices) {
|
||||||
|
int min=prices[0];//第一次买入的最小价格
|
||||||
|
int p1=0;//第一次卖出的收益
|
||||||
|
|
||||||
|
int max1=Integer.MIN_VALUE;//第二次买入的最大收益
|
||||||
|
int p2=0; //第二次卖出的收益
|
||||||
|
|
||||||
|
for(int i=1;i<prices.length;++i){
|
||||||
|
if(prices[i]>min)
|
||||||
|
p1=Math.max(p1,prices[i]-min);
|
||||||
|
else if(prices[i]<min)
|
||||||
|
min=prices[i];
|
||||||
|
p2= Math.max(p2,prices[i]+max1);
|
||||||
|
max1=Math.max(max1,p1-prices[i]);
|
||||||
|
|
||||||
|
}
|
||||||
|
return Math.max(p1,p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-21 11:20
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣188 买卖股票的最佳时机IV:
|
||||||
|
* 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
|
||||||
|
* 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
|
||||||
|
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T24_MaxProfit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:将上一题的最多2次变成了最多k次,可以根据上一题的思路进行状态转移方程的演变
|
||||||
|
* 1.dp[i][j%2==0] 由于是第一次买入,所以之前是没有收益的
|
||||||
|
* dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]-prices[i])
|
||||||
|
* 2.dp[i][j%2==1] 由于是第一次卖出
|
||||||
|
* dp[i][1]=max(dp[i-1][j],dp[i-1][j-1]+prices[i])
|
||||||
|
* 速度击败59.98% 内存击败70.44%
|
||||||
|
* @param k
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit(int k, int[] prices) {
|
||||||
|
|
||||||
|
int[][] dp = new int[prices.length][2 * k];
|
||||||
|
for (int i = 0; i < dp[0].length; i++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
dp[0][i] = -prices[0];
|
||||||
|
} else {
|
||||||
|
dp[0][i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
|
||||||
|
for (int j = 1; j < dp[0].length; j++) {
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[prices.length-1][2*k-1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化:
|
||||||
|
* 速度击败99.81% 内存击败93.44%
|
||||||
|
* @param k
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit1(int k, int[] prices) {
|
||||||
|
|
||||||
|
int[] dp = new int[2 * k];
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
dp[i] = -prices[0];
|
||||||
|
} else {
|
||||||
|
dp[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
dp[0] = Math.max(dp[0], -prices[i]);
|
||||||
|
for (int j = 1; j < dp.length; j++) {
|
||||||
|
if (j % 2 == 1) {
|
||||||
|
dp[j] = Math.max(dp[j], dp[j - 1] + prices[i]);
|
||||||
|
} else {
|
||||||
|
dp[j] = Math.max(dp[j], dp[j - 1] - prices[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[2*k-1];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-21 11:54
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣309题 最佳买卖股票时机含冷冻期:
|
||||||
|
* 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
|
||||||
|
* 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
|
||||||
|
* 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
|
||||||
|
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T25_MaxProfit {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] dp={1,2,3,0,2};
|
||||||
|
System.out.println(maxProfit(dp));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:不同之处在于 含冷冻期了 分为三种状态 手上有股票 手上没股票 处于冷冻期
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义: dp[i][0] dp[i][1] dp[i][2] 分别表示手上有股票 手上没股票(可操作) 手上没有股票(不可操作) 三种情况
|
||||||
|
* 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],dp[i-1][2]+prices[i])
|
||||||
|
* 3.dp[i][2] 手上有股票:昨天不可操作今天买了 昨天可操作今天买了 昨天有今天有
|
||||||
|
* dp[i][2]=max(dp[i-1][2],dp[i-1][1]-prices[i],dp[i-1][0]-prices[i])
|
||||||
|
*
|
||||||
|
* 3.dp初始化:dp[0][0]=0;dp[0][1]=0;dp[0][2]=-prices[0]
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit(int[] prices) {
|
||||||
|
|
||||||
|
int[][] dp = new int[prices.length][3];
|
||||||
|
dp[0][0] = 0;
|
||||||
|
dp[0][1] = 0;
|
||||||
|
dp[0][2] = -prices[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]);
|
||||||
|
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][2]+prices[i]);
|
||||||
|
dp[i][2]=Math.max(dp[i-1][2],dp[i-1][0]-prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp[dp.length-1][0],dp[dp.length-1][1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit1(int[] prices) {
|
||||||
|
|
||||||
|
|
||||||
|
int dp0 = 0;
|
||||||
|
int dp1 = 0;
|
||||||
|
int dp2 = -prices[0];
|
||||||
|
int temp0=0;
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
temp0=dp0;
|
||||||
|
dp0=Math.max(dp0,dp1);
|
||||||
|
dp1=Math.max(dp1,dp2+prices[i]);
|
||||||
|
dp2=Math.max(dp2,temp0-prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp0,dp1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-22 10:35
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣714题 买卖股票的最佳时机含手续费:
|
||||||
|
* 给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
|
||||||
|
* 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
|
||||||
|
* 返回获得利润的最大值。
|
||||||
|
* 注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T26_MaxProfit {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] prices = {1, 3, 2, 8, 4, 9};
|
||||||
|
int fee = 2;
|
||||||
|
System.out.println(maxProfit(prices,fee));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:仅是每笔交易的时候包含了手续费;在每次买的时候加上手续费即可
|
||||||
|
* 速度击败50.42% 内存击败89.43%
|
||||||
|
* @param prices
|
||||||
|
* @param fee
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit(int[] prices, int fee) {
|
||||||
|
|
||||||
|
int[][] dp = new int[prices.length][2];
|
||||||
|
|
||||||
|
dp[0][0] = 0;
|
||||||
|
dp[0][1] = -prices[0] - fee;
|
||||||
|
|
||||||
|
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[dp.length-1][0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败99.96% 内存击败36.47% 3ms
|
||||||
|
* @param prices
|
||||||
|
* @param fee
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit1(int[] prices, int fee) {
|
||||||
|
|
||||||
|
int dp0 = 0;
|
||||||
|
int dp1 = -prices[0] - fee;
|
||||||
|
int temp;
|
||||||
|
|
||||||
|
for (int i = 1; i < prices.length; i++) {
|
||||||
|
temp=dp0;
|
||||||
|
dp0=Math.max(dp0,dp1+prices[i]);
|
||||||
|
dp1=Math.max(dp1,temp-prices[i]-fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贪心算法: 核心在于 何时才真正卖出
|
||||||
|
* @param prices
|
||||||
|
* @param fee
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit2(int[] prices, int fee) {
|
||||||
|
|
||||||
|
int buy=prices[0]+fee;
|
||||||
|
int sum=0;
|
||||||
|
|
||||||
|
for (int price : prices) {
|
||||||
|
if(price+fee<buy){//后续如果比8大的数,如果在之前遇到了p + fee<buy的情况,那么后续就一定有正收益了,所以之前的可以放心卖
|
||||||
|
buy=price+fee;//寻找最小价格
|
||||||
|
}else if(price>buy){
|
||||||
|
sum += price - buy;//假装排除计算收益
|
||||||
|
buy = price;//后期如果有数比他大,那么就是计算加上的p-buy增值收益
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-22 10:58
|
||||||
|
*@Description:
|
||||||
|
* TODO leecode300 最长递增子序列:
|
||||||
|
* 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
|
||||||
|
* 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T27_LengthOfLIS {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:注意是子序列,可以删除前后元素,找最长 因为可以删除 所以可以将状态分为两种 :要当前的词 不要当前的词 TODO 不知道如何保存状态,未能做出
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示不要当前当前的词的最长序列长度 dp[i][1]表示要当前的词的最长序列长度
|
||||||
|
* 2.dp状态转移方程: //因为没法保存状态,所以只能暴力依次比较 时间复杂度O(n^2)
|
||||||
|
* 3.dp初始化:
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-22 11:47
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣674题 最长连续递增序列:
|
||||||
|
* 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
|
||||||
|
* 连续递增的子序列 可以由两个下标 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:不同之处在于 要连续递增 所以要维护的是一个子数组,由于是连续的,所以比较
|
||||||
|
* TODO DP五部曲;
|
||||||
|
* 1.dp定义:
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 3.dp初始化:
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int findLengthOfLCIS(int[] nums) {
|
||||||
|
|
||||||
|
int maxLength = 1;
|
||||||
|
int curLength = 1;
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
if (nums[i] > nums[i - 1]) {
|
||||||
|
curLength++;
|
||||||
|
} else {
|
||||||
|
curLength = 1;//小于就重置
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curLength > maxLength) maxLength = curLength;
|
||||||
|
}
|
||||||
|
return maxLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态规划法
|
||||||
|
* @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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-22 11:56
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣718题 最长重复子数组:
|
||||||
|
* 给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T29_FindLength {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums1 = {0, 1, 1, 1, 1};
|
||||||
|
int[] nums2 = {1, 0, 1, 0, 1};
|
||||||
|
System.out.println(findLength(nums1, nums2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义:dp[i][j]表示 使用nums1[0-i]和nums2[0-j]所返回的最长长度
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* dp[i][j] 可以是 不要nums2[j] 不要nums1[i] 或者nums[i]==nums[j]
|
||||||
|
* dp[i][j] if(nums[i-1]!=nums[j-1]) max(dp[i-1][j],dp[i][j-1])
|
||||||
|
* else dp[i-1][j-1]+1
|
||||||
|
* 3.dp初始化:为了初始化方便,取nums[i-1]!=nums[j-1]的关系 dp[i][0]=0; dp[0][j]=0;
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* @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;
|
||||||
|
//因为是子数组,不是子序列,所以一不想等,就直接等于0了,不做处理
|
||||||
|
if (result < dp[i][j]) result = dp[i][j];
|
||||||
|
// else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* @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 = dp.length - 1; j >= 1; j--) {
|
||||||
|
//返许遍历,因为dp[j]要用到dp[i-1]
|
||||||
|
if (nums1[i - 1] == nums2[j - 1]) {
|
||||||
|
dp[j] = dp[j - 1] + 1;
|
||||||
|
}else {
|
||||||
|
dp[j]=0; //TODO 特别需要注意: 如果不等需要重新赋值,避免不覆盖的情况;而二维的时候不用,因为不用重复利用
|
||||||
|
}
|
||||||
|
//因为是子数组,不是子序列,所以一不想等,就直接等于0了,不做处理
|
||||||
|
if (result < dp[j]) result = dp[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-23 10:12
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣1143 最长公共子序列:
|
||||||
|
* 给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
|
||||||
|
* 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
|
||||||
|
* 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
|
||||||
|
* 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T30_LongestCommonSubsequence {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
String text1 = "abcde", text2 = "ace";
|
||||||
|
System.out.println(longestCommonSubsequence(text2,text1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:不同之处在于子序列,所以可以删除
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示从text1[0-i]和text2[0-j]的最长子序列长度
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.if(text1[i]==text2[j]) dp[i][j]=dp[i-1][j-1]+1
|
||||||
|
* 2.else dp[i][j]=max(dp[i-1][j],dp[i][j-1])
|
||||||
|
* 3.dp初始化:考虑到初始化设置为text[i-1]==text[j-1] 则dp[i][0]全为0
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:以 text1 = "abcde", text2 = "ace" 为例:
|
||||||
|
* text1 [ 0 a b c d e]
|
||||||
|
* 0 0 0 0 0 0 0
|
||||||
|
* a 0 1 1 1 1 1
|
||||||
|
* c 0 1 1 2 2 2
|
||||||
|
* e 0 1 1 2 2 3
|
||||||
|
* 速度击败24.9% 内存击败33.8% 16ms
|
||||||
|
* @param text1
|
||||||
|
* @param text2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestCommonSubsequence(String text1, String text2) {
|
||||||
|
|
||||||
|
if (text1.length() < text2.length()) {
|
||||||
|
longestCommonSubsequence(text2, text1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
|
||||||
|
|
||||||
|
for (int i = 1; i <= text1.length(); i++) {
|
||||||
|
for (int j = 1; j <= text2.length(); j++) {
|
||||||
|
if (text1.charAt(i-1) == text2.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 dp[text1.length()][text2.length()];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-23 10:43
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣1035 不相交的线:
|
||||||
|
* 在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
|
||||||
|
* 现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:
|
||||||
|
* nums1[i] == nums2[j]
|
||||||
|
* 且绘制的直线不与任何其他连线(非水平线)相交。
|
||||||
|
* 请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
|
||||||
|
* 以这种方法绘制线条,并返回可以绘制的最大连线数。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T31_MaxUncrossedLines {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:本质上也是一个子序列的题,因为这题要求上就是 可以删除不能过
|
||||||
|
* 加上最上面的if之后 速度击败99.16% 内存击败49.86% 4ms
|
||||||
|
* @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];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-23 10:59
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣53题 最大子数组和:
|
||||||
|
* 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
|
||||||
|
* 子数组 是数组中的一个连续部分。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T32_MaxSubArray {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:注意子数组,所以不要当前数,就需要抛弃前面的所有数
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示不要nums[i]的最大和 dp[i][1]表示要nums[i]的最大和
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][0] 不要当前的数 分为 要之前的 和 都不要
|
||||||
|
* dp[i][0]=max(dp[i-1][1],0)
|
||||||
|
* 2.dp[i-1][1] 要当前的数 分为 不要之前的 和 要之前的
|
||||||
|
* dp[i][1]=max(nums[i],dp[i-1][1]+nums[1])
|
||||||
|
* 3.dp初始化:dp[0][0]=max(nums[0],0);dp[0][1]=nums[0]
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导: 以nums = [-2,1,-3,4,-1,2,1,-5,4]为例
|
||||||
|
* [-2 1 -3 4 -1 2 1 -5 4]
|
||||||
|
* i=0 0 0 1 0 4 3 5 6 1
|
||||||
|
* i=1 -2 1 -2 4 3 5 6 1 5
|
||||||
|
* 速度击败40.89% 内存击败46.24%
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray(int[] nums) {
|
||||||
|
|
||||||
|
|
||||||
|
int[] dp = new int[nums.length];
|
||||||
|
|
||||||
|
dp[0] = nums[0];
|
||||||
|
int max = nums[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
|
||||||
|
if (max < dp[i]) max = dp[i]; //子数组的题似乎都是在过程中寻找最值
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动数组优化
|
||||||
|
* 速度击败100% 内存击败63.24%
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray1(int[] nums) {
|
||||||
|
|
||||||
|
int dp0 = nums[0];
|
||||||
|
int max = nums[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
dp0 = Math.max(nums[i], dp0 + nums[i]);
|
||||||
|
if (max < dp0) max = dp0; //子数组的题似乎都是在过程中寻找最值
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贪心
|
||||||
|
* 速度击败100% 内存击败23.49% 1ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray2(int[] nums) {
|
||||||
|
|
||||||
|
int max = nums[0];
|
||||||
|
int curValue = nums[0];
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
if (curValue < 0) {
|
||||||
|
curValue = 0;
|
||||||
|
}
|
||||||
|
curValue += nums[i];
|
||||||
|
if (max < curValue) max = curValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return max;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-23 11:23
|
||||||
|
*@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", t = "ahbgdc";
|
||||||
|
System.out.println(isSubsequence1(s,t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:判断子序列,实际上还是子序列的题 递推公式类似
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示使用t[0-i]是否是s[0-j]的父序列
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* if(t[i]==s[j]) dp[i][j]=dp[i-1][j-1] 这两个相同就看看子序列是不是
|
||||||
|
* else dp[i][j]=dp[i-1][j] 去除掉之后是不是 需要注意的是,这里没有dp[i][j-1]
|
||||||
|
* 3.dp初始化:为了递推方便 设置为t[i-1]=s[j-1] 则dp[i][0]=true dp[0][j]=false
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败18.94% 内存击败10.33% 5ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence(String s, String t) {
|
||||||
|
|
||||||
|
boolean[][] dp = new boolean[t.length() + 1][s.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++) {
|
||||||
|
if (t.charAt(i - 1) == s.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1];
|
||||||
|
else dp[i][j] = dp[i - 1][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[t.length()][s.length()];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败34.1% 内存击败68.5%
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence1(String s, String t) {
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
dp[0] = true;
|
||||||
|
}
|
||||||
|
for (int i = 1; i <= t.length(); i++) {
|
||||||
|
for (int j = s.length(); j >0; j--) {
|
||||||
|
if (t.charAt(i - 1) == s.charAt(j - 1)) dp[j] = dp[j - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s.length()];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双指针法:时间复杂度度O(M+N)
|
||||||
|
* 速度击败88% 内存击败52.96% 1ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSubsequence2(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-23 11:52
|
||||||
|
*@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", t = "rabbit";
|
||||||
|
// String s = "babgbag", t = "bag";
|
||||||
|
System.out.println(numDistinct1(s,t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:本质上实际上是从是不是子序列变为了 有多少个子序列的问题
|
||||||
|
* TODO 不同的子序列:
|
||||||
|
* 1.dp定义: dp[i][j]表示使用s[0-i]能凑出多少个t[0-j]
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* dp[i][j]的状态分为两种 :
|
||||||
|
* if s[i]==t[j] 原本能继续凑出 dp[i][j]=dp[i-1][j-1]+dp[i-1][j]
|
||||||
|
* else 不能继续凑出 dp[i][j]=dp[i-1][j]
|
||||||
|
* 3.dp初始化:
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:以s = "rabbbit", t = "rabbit"为例
|
||||||
|
* [0 r a b b i t]
|
||||||
|
* 0: 1 0 0 0 0 0 0
|
||||||
|
* r: 1 1 0 0 0 0 0
|
||||||
|
* a: 1 1 1 0 0 0 0
|
||||||
|
* b: 1 1 1 1 0 0 0
|
||||||
|
* b: 1 1 1 2 1 0 0
|
||||||
|
* b: 1 1 1 3 3 0 0
|
||||||
|
* i: 1 1 1 3 3 3 0
|
||||||
|
* t: 1 1 1 3 3 3 3
|
||||||
|
* 速度击败26.62% 内存击败32.5% 16ms
|
||||||
|
* @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++) {
|
||||||
|
//证明之前匹配上过,那么在之前匹配上的基础上+后来匹配上的次数:一部分是用s[i - 1]来匹配;一部分是不用s[i - 1]来匹配
|
||||||
|
if (s.charAt(i - 1) == t.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
|
||||||
|
else dp[i][j] = dp[i - 1][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[s.length()][t.length()];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败73.25% 内存击败99.63% 11ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numDistinct1(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 = t.length(); j >0; j--) {//因为要使用之前的j-1所以就反向遍历
|
||||||
|
if (s.charAt(i - 1) == t.charAt(j - 1)) dp[j] += dp[j - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[t.length()];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-24 10:32
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣583题 两个字符串的删除操作:
|
||||||
|
* 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。
|
||||||
|
* 每步 可以删除任意一个字符串中的一个字符。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T35_MinDistance {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
String word1 = "leetcode", word2 = "etco";
|
||||||
|
System.out.println(minDistance(word1,word2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 每个字符串都能删
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义:dp[i][j]表示使用word1[i]变为word2[j]需要删除的数量
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.当word1[i]==word2[j]时 则不需要多加操作
|
||||||
|
* dp[i][j]=dp[i-1][j-1]
|
||||||
|
* 2.当word1[i]!=word2[j]时 则需要在之前多加操作
|
||||||
|
* dp[i][j]= min(dp[i-1][j]+1,dp[i][j-1]+1)
|
||||||
|
* 3.dp初始化: dp[i][0]=i dp[0][j]=j
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:以word1 = "leetcode", word2 = "etco"为例
|
||||||
|
* [0 e t c o]
|
||||||
|
* 0 0 1 2 3 4
|
||||||
|
* l 1 2 3 4 5
|
||||||
|
* e 2 1 2 3 4
|
||||||
|
* e 3
|
||||||
|
* t 4
|
||||||
|
* c 5
|
||||||
|
* o 6
|
||||||
|
* d 7
|
||||||
|
* e 8
|
||||||
|
* 速度击败62.53% 内存击败39.37% 7ms
|
||||||
|
* @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 = 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 (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()];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-24 11:09
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣72题 编辑距离:
|
||||||
|
* 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
|
||||||
|
* 你可以对一个单词进行如下三种操作:
|
||||||
|
* 插入一个字符
|
||||||
|
* 删除一个字符
|
||||||
|
* 替换一个字符
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T36_MinDistance {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
String word1 = "horse", word2 = "ros";
|
||||||
|
System.out.println(minDistance(word1,word2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:更复杂的地方在于 编辑存在三种操作: 应该如何处理
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义:dp[i][j]表示word1[0-i]通过三种操作最少能花多少步变成word2[0-j]
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.当word1[i]!=word1[j]时 删除 修改 插入
|
||||||
|
* dp[i][j]=min(dp[i-1][j]+1,dp[i-1][j-1]+1,dp[i][j-1]+1)
|
||||||
|
* else dp[i][j]=dp[i-1][j-1]
|
||||||
|
* 3.dp初始化:
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败45.95% 内存击败14.7% 5ms
|
||||||
|
* @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 = 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(word1.charAt(i-1)==word2.charAt(j-1)) dp[i][j]=dp[i-1][j-1];
|
||||||
|
else dp[i][j]=Math.min(Math.min(dp[i-1][j]+1,dp[i-1][j-1]+1),dp[i][j-1]+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[word1.length()][word2.length()];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toCharArray()优化
|
||||||
|
* 速度击败95.84% 内存击败60.85%
|
||||||
|
* @param word1
|
||||||
|
* @param word2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int minDistance1(String word1, String word2) {
|
||||||
|
|
||||||
|
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
|
||||||
|
char[] char1 = word1.toCharArray();
|
||||||
|
char[] char2 = word2.toCharArray();
|
||||||
|
|
||||||
|
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-1][j]+1,dp[i-1][j-1]+1),dp[i][j-1]+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[word1.length()][word2.length()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-26 10:08
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣647题 回文子串:
|
||||||
|
* 给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
|
||||||
|
* 回文字符串 是正着读和倒过来读一样的字符串。
|
||||||
|
* 子字符串 是字符串中的由连续字符组成的一个序列。
|
||||||
|
* 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T37_CountSubstrings {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
// String s="aaa";
|
||||||
|
String s="abc";
|
||||||
|
System.out.println(countSubstrings(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:和编辑距离系列有异曲同工之妙,也可以通过前面的是不是来判断后面的是不是 如aa是;baab是
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义:dp[i][j]表示以j开头,以i结尾的word是不是回文字符串
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* if s[i]==s[j]
|
||||||
|
* dp[i][j]=dp[i-1][j+1]
|
||||||
|
* else
|
||||||
|
* 3.dp初始化: dp[i][i]=true
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导: 以s = "aaa"为例
|
||||||
|
* [a a a]
|
||||||
|
* a t t t
|
||||||
|
* a t t t
|
||||||
|
* a t t t
|
||||||
|
* 速度击败55.35% 内存击败43.32% 7ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int countSubstrings(String s) {
|
||||||
|
int length = s.length();
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
boolean[][] dp = new boolean[length][length];
|
||||||
|
int result = length;//i=j
|
||||||
|
|
||||||
|
//将初始化蕴藏在实际for里面
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
for (int j = 0; j < length; j++) {
|
||||||
|
if (i <= j) {
|
||||||
|
dp[i][j] = true;
|
||||||
|
} else if (chars[i] == chars[j]) {
|
||||||
|
dp[i][j] = dp[i - 1][j + 1];
|
||||||
|
if (dp[i][j]) result++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代码随想录解法:只有当char[i]==char[j]的时候才进行判断
|
||||||
|
* 速度击败62.97% 内存击败25.91% 4ms
|
||||||
|
* 快在只需要判断下三角
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int countSubstrings1(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){//情况一a 情况二aa
|
||||||
|
// result++;
|
||||||
|
// dp[i][j]=true;
|
||||||
|
// }else if(dp[i-1][j+1]){//情况三bab
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双指针法:可以以一个单词为中心;也可以以两个单词为中心
|
||||||
|
* 速度击败99.97% 内存击败61.2% 1ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int countSubstrings2(String s) {
|
||||||
|
int result = 0;
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
for (int i = 0; i < s.length(); i++) {//因为for是一直向前的,所以一定不会有重复
|
||||||
|
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,89 @@
|
||||||
|
package com.markilue.leecode.dynamic.second;
|
||||||
|
|
||||||
|
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.dynamic.second
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-26 10:58
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣516题 最长回文子序列:
|
||||||
|
* 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
|
||||||
|
* 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T38_LongestPalindromeSubseq {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// String s = "bbbab";
|
||||||
|
String s = "cbbd";
|
||||||
|
System.out.println(longestPalindromeSubseq(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:还是分为当前两个字符是不是相等的两种情况;由于是子序列,所以存在继承关系
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示以j开头,以i结尾的子序列的最长回文子序列
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.s[i]==s[j]
|
||||||
|
* dp[i][j]=dp[i-1][j+1]+2
|
||||||
|
* 2.s[i]!=s[j] 删一个看那个更大
|
||||||
|
* dp[i][j]=max(dp[i-1][j],dp[i][j+1])
|
||||||
|
* 3.dp初始化: dp[i][i]=1
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 两个if速度变慢 速度击败81.32% 内存击败49.22% 27ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestPalindromeSubseq(String s) {
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
int[][] dp = new int[chars.length][chars.length];
|
||||||
|
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
for (int j = i; j >= 0; j--) {//由于需要j+1
|
||||||
|
if (chars[i] == chars[j]) {
|
||||||
|
if (j == i) dp[i][j] = 1;//包含初始化
|
||||||
|
else 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[chars.length - 1][0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化:一个if,初始化放外面
|
||||||
|
* 速度击败99.36% 内存击败15.5% 13ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestPalindromeSubseq1(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];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-27 10:47
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣第1题:
|
||||||
|
* 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
|
||||||
|
* 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
|
||||||
|
* 你可以按任意顺序返回答案。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T01_TwoSum {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] nums = {2, 7, 11, 15};
|
||||||
|
int target = 9;
|
||||||
|
System.out.println(twoSum(nums,target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* map记录
|
||||||
|
* 速度击败77.1% 内存击败30.65% 2ms
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[] twoSum(int[] nums, int target) {
|
||||||
|
|
||||||
|
HashMap<Integer, Integer> map = new HashMap<>();//<need,index>
|
||||||
|
int need = 0;
|
||||||
|
Integer index;
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
need = target - nums[i];
|
||||||
|
index = map.getOrDefault(need, -1);
|
||||||
|
if (index != -1) {
|
||||||
|
return new int[]{index, i};//找到了
|
||||||
|
} else {
|
||||||
|
map.put(nums[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[2];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快
|
||||||
|
* 类似于双指针
|
||||||
|
* 速度击败100% 内存击败30.65% 0ms
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[] twoSum1(int[] nums, int target) {
|
||||||
|
Map<Integer,Integer> map = new HashMap<>();
|
||||||
|
|
||||||
|
int[] arr = new int[2];
|
||||||
|
|
||||||
|
int left = 0;
|
||||||
|
int right = nums.length-1;
|
||||||
|
|
||||||
|
while(left<=right){
|
||||||
|
if(map.containsKey(nums[left])){
|
||||||
|
arr[0] = map.get(nums[left]);
|
||||||
|
arr[1] = left;
|
||||||
|
return arr;
|
||||||
|
} else {
|
||||||
|
map.put(target-nums[left],left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(map.containsKey(nums[right])){
|
||||||
|
arr[0] = map.get(nums[right]);
|
||||||
|
arr[1] = right;
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
} else {
|
||||||
|
map.put(target-nums[right],right);
|
||||||
|
}
|
||||||
|
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-27 11:15
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣2题 两数相加:
|
||||||
|
* 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
|
||||||
|
* 请你将两个数相加,并以相同形式返回一个表示和的链表。
|
||||||
|
* 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T02_AddTwoNumbers {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:递归法 每个节点只能存储一个数字,多的数字需要进位
|
||||||
|
* 速度击败100% 内存击败45.35% 1ms
|
||||||
|
* @param l1
|
||||||
|
* @param l2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
|
||||||
|
|
||||||
|
return addTwoNumbers(l1, l2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param l1
|
||||||
|
* @param l2
|
||||||
|
* @param flag 是否需要进位
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode addTwoNumbers(ListNode l1, ListNode l2, boolean flag) {
|
||||||
|
//为了公共统一,如果某一个为null,可以给他初始化
|
||||||
|
if (l1 == null && l2 == null) {
|
||||||
|
if (flag) return new ListNode(1);
|
||||||
|
return null;
|
||||||
|
} else if (l1 == null) {
|
||||||
|
l1 = new ListNode(0);
|
||||||
|
} else if (l2 == null) {
|
||||||
|
l2 = new ListNode(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode fakeHead = new ListNode(0);
|
||||||
|
int var = l1.val + l2.val;
|
||||||
|
if (flag) {
|
||||||
|
var += 1;
|
||||||
|
}
|
||||||
|
fakeHead.val = var % 10;
|
||||||
|
fakeHead.next = addTwoNumbers(l1.next, l2.next, var >= 10);
|
||||||
|
|
||||||
|
return fakeHead;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方解法: 使用head,tail指针进行记录 值得借鉴
|
||||||
|
* 速度击败100% 内存击败94.81% 1ms
|
||||||
|
* @param l1
|
||||||
|
* @param l2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode addTwoNumbers1(ListNode l1, ListNode l2) {
|
||||||
|
ListNode head = null, tail = null;
|
||||||
|
int carry = 0;
|
||||||
|
while (l1 != null || l2 != null) {
|
||||||
|
int n1 = l1 != null ? l1.val : 0;
|
||||||
|
int n2 = l2 != null ? l2.val : 0;
|
||||||
|
int sum = n1 + n2 + carry;
|
||||||
|
if (head == null) {
|
||||||
|
head = tail = new ListNode(sum % 10);
|
||||||
|
} else {
|
||||||
|
tail.next = new ListNode(sum % 10);
|
||||||
|
tail = tail.next;
|
||||||
|
}
|
||||||
|
carry = sum / 10;
|
||||||
|
if (l1 != null) {
|
||||||
|
l1 = l1.next;
|
||||||
|
}
|
||||||
|
if (l2 != null) {
|
||||||
|
l2 = l2.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (carry > 0) {
|
||||||
|
tail.next = new ListNode(carry);
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-27 12:46
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣3题 无重复字符的最长子串:
|
||||||
|
* 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T03_LengthOfLongestSubstring {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "dvdf";
|
||||||
|
String s1 = "abba";
|
||||||
|
System.out.println(lengthOfLongestSubstring(s1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:注意是子串,不是子序列
|
||||||
|
* 使用一个hashmap记录之前是否有过这个单词
|
||||||
|
* 速度击败87.4% 内存击败75.84% 4ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int lengthOfLongestSubstring(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
Map<Character, Integer> map = new HashMap<>();//<key,index>
|
||||||
|
int maxLength = 0;
|
||||||
|
int lastIndex = 0;
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
if (map.containsKey(chars[i])) {
|
||||||
|
lastIndex = Math.max(lastIndex, map.get(chars[i]) + 1);//上次存在,索引就挪到这个词的下一个
|
||||||
|
}
|
||||||
|
length = i - lastIndex + 1;//长度
|
||||||
|
if (length > maxLength) maxLength = length;
|
||||||
|
map.put(chars[i], i);//如果存在就会覆盖
|
||||||
|
|
||||||
|
}
|
||||||
|
return maxLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:使用数组来代替map
|
||||||
|
* 速度击败1005 内存击败68.17% 1ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int lengthOfLongestSubstring1(String s) {
|
||||||
|
int[] last = new int[128];
|
||||||
|
for(int i = 0; i < 128; i++) {
|
||||||
|
last[i] = -1;
|
||||||
|
}
|
||||||
|
int n = s.length();
|
||||||
|
|
||||||
|
int res = 0;
|
||||||
|
int start = 0;
|
||||||
|
for(int i = 0; i < n; i++) {
|
||||||
|
int index = s.charAt(i);
|
||||||
|
start = Math.max(start, last[index] + 1);
|
||||||
|
res = Math.max(res, i - start + 1);
|
||||||
|
last[index] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论区动态规划法
|
||||||
|
* 速度击败100% 内存击败91.56% 1ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int lengthOfLongestSubstring2(String s) {
|
||||||
|
int m = s.length();
|
||||||
|
if(m==0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int num =1; //初始值表示以s的第一个字符为结束的不重复的最长子串
|
||||||
|
int max =1;
|
||||||
|
for(int i=1;i<m;i++){
|
||||||
|
int index = s.indexOf(s.charAt(i),i-num);
|
||||||
|
if(index<i) { //num更新,表示以s的第i+1个字符为结束的不重复的最长子串
|
||||||
|
num = i-index;
|
||||||
|
} else {
|
||||||
|
num = num+1;
|
||||||
|
}
|
||||||
|
max = Math.max(max,num);
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-27 13:25
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣4题 寻找两个正序数组的中位数
|
||||||
|
* 给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
|
||||||
|
* 算法的时间复杂度应该为 O(log (m+n)) 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T04_FindMedianSortedArrays {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:先逐个找,找到时间复杂度为O(m+n)
|
||||||
|
* 速度击败100% 内存击败25.42% 1ms
|
||||||
|
* @param nums1
|
||||||
|
* @param nums2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
|
||||||
|
|
||||||
|
int m = nums1.length;
|
||||||
|
int n = nums2.length;
|
||||||
|
int len = m + n;
|
||||||
|
int left = -1, right = -1;
|
||||||
|
int aStart = 0, bStart = 0;
|
||||||
|
for (int i = 0; i <= len / 2; i++) {//必须遍历到threshold的位置才出去
|
||||||
|
left = right;
|
||||||
|
if (aStart < m && (bStart >= n || nums1[aStart] < nums2[bStart])) {//判断条件是关键
|
||||||
|
right = nums1[aStart++];
|
||||||
|
} else {
|
||||||
|
right = nums2[bStart++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((len & 1) == 0)
|
||||||
|
return (left + right) / 2.0;
|
||||||
|
else
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间复杂度O(log(m+n)) 每次比较k/2的位置 抛弃一半
|
||||||
|
* 速度击败100% 内存击败45.86% 1ms
|
||||||
|
* @param nums1
|
||||||
|
* @param nums2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double findMedianSortedArrays1(int[] nums1, int[] nums2) {
|
||||||
|
int n = nums1.length;
|
||||||
|
int m = nums2.length;
|
||||||
|
int left = (n + m + 1) / 2;
|
||||||
|
int right = (n + m + 2) / 2;
|
||||||
|
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
|
||||||
|
return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
|
||||||
|
int len1 = end1 - start1 + 1;
|
||||||
|
int len2 = end2 - start2 + 1;
|
||||||
|
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
|
||||||
|
if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
|
||||||
|
if (len1 == 0) return nums2[start2 + k - 1];
|
||||||
|
|
||||||
|
if (k == 1) return Math.min(nums1[start1], nums2[start2]);
|
||||||
|
//会直接比较k/2的位置
|
||||||
|
int i = start1 + Math.min(len1, k / 2) - 1;
|
||||||
|
int j = start2 + Math.min(len2, k / 2) - 1;
|
||||||
|
|
||||||
|
if (nums1[i] > nums2[j]) {
|
||||||
|
return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-28 10:10
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣5题 最长回文子串:
|
||||||
|
* 给你一个字符串 s,找到 s 中最长的回文子串。
|
||||||
|
* 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T05_LongestPalindrome {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "babad";
|
||||||
|
System.out.println(longestPalindrome1(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:最长回文子串,则不能删除,一旦不是回文子串则直接归零
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义:dp[i][j]表示以j开始以i结尾的子串是不是回文子串
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.char[i]==char[j]
|
||||||
|
* dp[i][j]=dp[i-1][j+1]
|
||||||
|
* 3.dp初始化: dp[i][i]=true
|
||||||
|
* 4.dp遍历顺序:从上往下
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败51.19% 内存击败7.46% 79ms
|
||||||
|
* 修改后 62ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String longestPalindrome(String s) {
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
boolean[][] dp = new boolean[chars.length][chars.length];
|
||||||
|
int maxLength = 0;
|
||||||
|
int begin = 0;
|
||||||
|
int end = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
if (chars[i] == chars[j]) {
|
||||||
|
if (i <= j + 1) {
|
||||||
|
dp[i][j] = true;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = dp[i - 1][j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dp[i][j]) {
|
||||||
|
if (maxLength < i - j + 1) {
|
||||||
|
maxLength = i - j + 1;
|
||||||
|
begin = j;
|
||||||
|
end = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.substring(begin, end);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPalindrome(char[] chars, int start, int end) {
|
||||||
|
|
||||||
|
while (start < end) {
|
||||||
|
if (chars[start++] != chars[end--]) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一维dp优化
|
||||||
|
* 速度击败54.74% 内存击败77.79% 42ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String longestPalindrome1(String s) {
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[chars.length];
|
||||||
|
int maxLength = 0;
|
||||||
|
int begin = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
for (int j = 0; j <= i; j++) {
|
||||||
|
if (chars[i] == chars[j]) {
|
||||||
|
if (i <= j + 1) {
|
||||||
|
dp[j] = true;
|
||||||
|
} else {
|
||||||
|
dp[j] = dp[j + 1];
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
dp[j]=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dp[j]&&maxLength < i - j + 1) {
|
||||||
|
|
||||||
|
maxLength = i - j + 1;
|
||||||
|
begin = j;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.substring(begin, begin+maxLength);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:
|
||||||
|
* 分别计算考虑左边;考虑右边;和以i为中心,本质上应该还是O(N^2)
|
||||||
|
* 速度击败93.2% 内存击败78.66% 7ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String longestPalindrome2(String s) {
|
||||||
|
char[] charStr = s.toCharArray();
|
||||||
|
|
||||||
|
int maxlen = 0;
|
||||||
|
int maxStart = 0;
|
||||||
|
for (int i = 0; i < charStr.length; i++) {
|
||||||
|
int len = 1;
|
||||||
|
int left = i - 1;
|
||||||
|
int right = i + 1;
|
||||||
|
while (left >= 0 && charStr[i] == charStr[left]) {//查找左边
|
||||||
|
len++;
|
||||||
|
left--;
|
||||||
|
}
|
||||||
|
while (right < charStr.length && charStr[i] == charStr[right]) {//查找右边
|
||||||
|
len++;
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
while (left >= 0 && right < charStr.length && charStr[left] == charStr[right]) {//以i为中心
|
||||||
|
len += 2;
|
||||||
|
left--;
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
if (len > maxlen) {
|
||||||
|
maxlen = len;
|
||||||
|
maxStart = left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.substring(maxStart + 1, maxlen + maxStart + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中心扩展法:看以两个为中心,和以一个为中心是不是回文子串
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String longestPalindrome3(String s) {
|
||||||
|
if (s == null || s.length() < 1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int start = 0, end = 0;
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
int len1 = expandAroundCenter(s, i, i);
|
||||||
|
int len2 = expandAroundCenter(s, i, i + 1);
|
||||||
|
int len = Math.max(len1, len2);
|
||||||
|
if (len > end - start) {
|
||||||
|
start = i - (len - 1) / 2;
|
||||||
|
end = i + len / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s.substring(start, end + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int expandAroundCenter(String s, int left, int right) {
|
||||||
|
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
|
||||||
|
--left;
|
||||||
|
++right;
|
||||||
|
}
|
||||||
|
return right - left - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.sun.java.swing.plaf.windows.WindowsDesktopIconUI;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-02-28 11:52
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣6题 N字形变形:
|
||||||
|
* 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
|
||||||
|
* 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
|
||||||
|
* P A H N
|
||||||
|
* A P L S I I G
|
||||||
|
* Y I R
|
||||||
|
* 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
|
||||||
|
* 请你实现这个将字符串进行指定行数变换的函数:
|
||||||
|
* string convert(string s, int numRows);
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T06_Convert {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// String s = "ABC";
|
||||||
|
String s = "PAYPALISHIRING";
|
||||||
|
System.out.println(convert3(s, 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:先计算一共需要多少列才能记录
|
||||||
|
* 速度击败21.1% 内存击败22.88% 26ms
|
||||||
|
* @param s
|
||||||
|
* @param numRows
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String convert(String s, int numRows) {
|
||||||
|
|
||||||
|
//先计算一共需要多少列才能记录
|
||||||
|
int length = s.length();
|
||||||
|
if (numRows >= length || numRows == 1) return s;
|
||||||
|
char[] chars1 = s.toCharArray();
|
||||||
|
int index = 0;
|
||||||
|
int col = length / 2;
|
||||||
|
char[][] chars = new char[numRows][col + 1];
|
||||||
|
|
||||||
|
for (int k = 0; k < length / (numRows - 1) && index < length; k++) {
|
||||||
|
int i = 0;
|
||||||
|
int j = 0;
|
||||||
|
for (; i < numRows - 1 && index < length; i++) {
|
||||||
|
chars[i][k * (numRows - 1)] = chars1[index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i > 0 && index < length; i--, j++) {
|
||||||
|
chars[i][k * (numRows - 1) + j] = chars1[index++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
for (int j = 0; j < chars[i].length; j++) {
|
||||||
|
if (chars[i][j] != '\u0000')
|
||||||
|
sb.append(chars[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方思路:本质上和自己的是一致的,但是判断依据进行了简化
|
||||||
|
* 速度击败18.75% 内存击败12.36% 35ms
|
||||||
|
* @param s
|
||||||
|
* @param numRows
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String convert1(String s, int numRows) {
|
||||||
|
int n = s.length(), r = numRows;
|
||||||
|
if (r == 1 || r >= n) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
int t = r * 2 - 2;
|
||||||
|
int c = (n + t - 1) / t * (r - 1);
|
||||||
|
char[][] mat = new char[r][c];
|
||||||
|
for (int i = 0, x = 0, y = 0; i < n; ++i) {
|
||||||
|
mat[x][y] = s.charAt(i);
|
||||||
|
if (i % t < r - 1) {
|
||||||
|
++x; // 向下移动
|
||||||
|
} else {
|
||||||
|
--x;
|
||||||
|
++y; // 向右上移动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuffer ans = new StringBuffer();
|
||||||
|
for (char[] row : mat) {
|
||||||
|
for (char ch : row) {
|
||||||
|
if (ch != 0) {
|
||||||
|
ans.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:直接使用stringbuilder进行append
|
||||||
|
* 速度击败43.99% 内存击败56.51% 8ms
|
||||||
|
* @param s
|
||||||
|
* @param numRows
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String convert2(String s, int numRows) {
|
||||||
|
int n = s.length(), r = numRows;
|
||||||
|
if (r == 1 || r >= n) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
StringBuffer[] mat = new StringBuffer[r];
|
||||||
|
for (int i = 0; i < r; ++i) {
|
||||||
|
mat[i] = new StringBuffer();
|
||||||
|
}
|
||||||
|
for (int i = 0, x = 0, t = r * 2 - 2; i < n; ++i) {
|
||||||
|
mat[x].append(s.charAt(i));
|
||||||
|
if (i % t < r - 1) {
|
||||||
|
++x;
|
||||||
|
} else {
|
||||||
|
--x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringBuffer ans = new StringBuffer();
|
||||||
|
for (StringBuffer row : mat) {
|
||||||
|
ans.append(row);
|
||||||
|
}
|
||||||
|
return ans.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方直接构造法:
|
||||||
|
* 把其中的0,1,2,3改成循环的i变量,就是代码。 吐槽一句:这个表格编辑的时候看起来整齐,预览却不整齐,调整了好半天,官方大大能不能优化一下。
|
||||||
|
* 0 0+t 0+2t 0+3t
|
||||||
|
* 1 t-1 1+t 0+2t-1 1+2t 0+3t-1 1+3t
|
||||||
|
* 2 t-2 2+t 0+2t-2 2+2t 0+3t-2 2+3t
|
||||||
|
* 3 3+t 3+2t 3+3t
|
||||||
|
* 好像暂时有错误
|
||||||
|
* @param s
|
||||||
|
* @param numRows
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String convert3(String s, int numRows) {
|
||||||
|
int n = s.length(), r = numRows;
|
||||||
|
if (r == 1 || r >= n) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int t = r * 2 - 2;
|
||||||
|
for (int i = 0; i < r; i++) {// 枚举矩阵的行
|
||||||
|
for (int j = 0; j + i < n; j += t) {// 枚举每个周期的起始下标
|
||||||
|
sb.append(s.charAt(j + i));// 当前周期的第一个字符
|
||||||
|
if (0 < i && i < r - 1 && j + t - i < n) {
|
||||||
|
sb.append(s.charAt(j + t - 1));// 当前周期的第二个字符
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.sun.deploy.util.StringUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-01 10:11
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣7题 整数反转:
|
||||||
|
* 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
|
||||||
|
* 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
|
||||||
|
* 假设环境不允许存储 64 位整数(有符号或无符号)。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T07_Reverse {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// String s = "00205";
|
||||||
|
// System.out.println(Integer.parseInt(s));205
|
||||||
|
|
||||||
|
// int x=-120;
|
||||||
|
int x = -2147483648;
|
||||||
|
System.out.println(reverse1(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:把它转化为字符串,在进行反转
|
||||||
|
* 速度击败17.3% 内存击败53.2% 2ms
|
||||||
|
* @param x
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int reverse(int x) {
|
||||||
|
boolean flag = true;//记录这个数是否是正数
|
||||||
|
if (x < 0) flag = false;
|
||||||
|
|
||||||
|
String s = "" + Math.abs((long) x);//这里必须要转为long,不然Integer.Min不能进行反转
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
//反转字符串
|
||||||
|
reverseString(chars);
|
||||||
|
long result = Long.parseLong(new String(chars));
|
||||||
|
if (!flag) result = -result;
|
||||||
|
|
||||||
|
if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) return 0;
|
||||||
|
|
||||||
|
return (int) result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reverseString(char[] chars) {
|
||||||
|
int left = 0;
|
||||||
|
int right = chars.length - 1;
|
||||||
|
char temp;
|
||||||
|
while (left < right) {
|
||||||
|
temp = chars[left];
|
||||||
|
chars[left] = chars[right];
|
||||||
|
chars[right] = temp;
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:考虑直接把它当整数进行反转
|
||||||
|
* 速度击败100% 内存击败56.5% 0ms
|
||||||
|
* @param x
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int reverse1(int x) {
|
||||||
|
boolean flag = true;//记录这个数是否是正数
|
||||||
|
if (x < 0) flag = false;
|
||||||
|
|
||||||
|
long y = (long) x;
|
||||||
|
long result = 0;
|
||||||
|
|
||||||
|
while (y != 0) {
|
||||||
|
result = result * 10 + y % 10;
|
||||||
|
y /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result > Integer.MAX_VALUE || result < Integer.MIN_VALUE) return 0;
|
||||||
|
|
||||||
|
|
||||||
|
return (int) result;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-01 10:53
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣8题 字符串转换整数(atoi):
|
||||||
|
* 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
|
||||||
|
* 函数 myAtoi(string s) 的算法如下:
|
||||||
|
* 读入字符串并丢弃无用的前导空格
|
||||||
|
* 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
|
||||||
|
* 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
|
||||||
|
* 将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
|
||||||
|
* 如果整数数超过 32 位有符号整数范围 [−231, 231 − 1] ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1 。
|
||||||
|
* 返回整数作为最终结果。
|
||||||
|
* 注意:
|
||||||
|
* 本题中的空白字符只包括空格字符 ' ' 。
|
||||||
|
* 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T08_MyAtoi {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = " ss-125-sa7894e ";
|
||||||
|
String s1 = " words and 987 ";
|
||||||
|
String s2 = " wor98ds and 7 ";
|
||||||
|
String s3 = " 9words and 87 ";
|
||||||
|
String s5 = " 9 8words and 87 ";
|
||||||
|
String s6 = " -9 8words and 87 ";
|
||||||
|
String s4 = "2147483648";
|
||||||
|
String s7 = "20000000000000000000";
|
||||||
|
// System.out.println(myAtoi(s));
|
||||||
|
// System.out.println(myAtoi(s1));
|
||||||
|
// System.out.println(myAtoi(s2));
|
||||||
|
// System.out.println(myAtoi(s3));
|
||||||
|
System.out.println(myAtoi(s4));
|
||||||
|
System.out.println(myAtoi(s5));
|
||||||
|
System.out.println(myAtoi(s6));
|
||||||
|
System.out.println(myAtoi(s7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:利用StringBuilder记录
|
||||||
|
* 速度击败100% 内存击败84.23%
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int myAtoi(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
boolean flag = false;//记录是否已经开始匹配到数字 :避免后续有 ‘-’的情况
|
||||||
|
|
||||||
|
for (char aChar : chars) {
|
||||||
|
if (((aChar == '-' || aChar == '+') && !flag) || (aChar - '0' >= 0 && aChar - '0' <= 9)) {
|
||||||
|
//是数字;或者是开头的-
|
||||||
|
sb.append(aChar);
|
||||||
|
flag = true;
|
||||||
|
} else if (!flag && aChar == ' ') {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sb.length() == 0 || (sb.length() == 1 && (sb.charAt(0) == '-' || sb.charAt(0) == '+'))) return 0;
|
||||||
|
|
||||||
|
// long result = Long.parseLong(sb.toString());//超出长度
|
||||||
|
boolean flag1 = true;//记录这个数是不是正数
|
||||||
|
long result = 0;
|
||||||
|
int startIndex = 0;
|
||||||
|
if (sb.charAt(0) == '-') {
|
||||||
|
flag1 = false;
|
||||||
|
startIndex = 1;
|
||||||
|
} else if (sb.charAt(0) == '+') {
|
||||||
|
startIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startIndex; i < sb.length(); i++) {
|
||||||
|
|
||||||
|
if (flag1) {
|
||||||
|
result = result * 10 + (sb.charAt(i) - '0');
|
||||||
|
} else {
|
||||||
|
result = result * 10 - (sb.charAt(i) - '0');
|
||||||
|
}
|
||||||
|
if (result < Integer.MIN_VALUE) return Integer.MIN_VALUE;
|
||||||
|
if (result > Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方解法:有限状态机
|
||||||
|
* @param str
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int myAtoi1(String str) {
|
||||||
|
Automaton automaton = new Automaton();
|
||||||
|
int length = str.length();
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
automaton.get(str.charAt(i));
|
||||||
|
}
|
||||||
|
return (int) (automaton.sign * automaton.ans);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Automaton {
|
||||||
|
public int sign = 1;
|
||||||
|
public long ans = 0;
|
||||||
|
private String state = "start";
|
||||||
|
private Map<String, String[]> table = new HashMap<String, String[]>() {{
|
||||||
|
put("start", new String[]{"start", "signed", "in_number", "end"});
|
||||||
|
put("signed", new String[]{"end", "end", "in_number", "end"});
|
||||||
|
put("in_number", new String[]{"end", "end", "in_number", "end"});
|
||||||
|
put("end", new String[]{"end", "end", "end", "end"});
|
||||||
|
}};
|
||||||
|
|
||||||
|
public void get(char c) {
|
||||||
|
state = table.get(state)[get_col(c)];
|
||||||
|
if ("in_number".equals(state)) {
|
||||||
|
ans = ans * 10 + c - '0';
|
||||||
|
ans = sign == 1 ? Math.min(ans, (long) Integer.MAX_VALUE) : Math.min(ans, -(long) Integer.MIN_VALUE);
|
||||||
|
} else if ("signed".equals(state)) {
|
||||||
|
sign = c == '+' ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int get_col(char c) {
|
||||||
|
if (c == ' ') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (c == '+' || c == '-') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-01 12:09
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣11题 盛最多的水:
|
||||||
|
* 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
|
||||||
|
* 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
|
||||||
|
* 返回容器可以储存的最大水量。
|
||||||
|
* 说明:你不能倾斜容器。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T09_MaxArea {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] height={1,8,6,2,5,4,8,3,7};
|
||||||
|
int[] height1={1,0,0,0,0,0,0,2,2};
|
||||||
|
System.out.println(maxArea1(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:似乎可以使用动态规划:
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示以i开头以j结尾的容器能盛多少水
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][j]<min(nums[i],nums[j-1])
|
||||||
|
* dp[i][j]=dp[i][j-1]-(j-1-i)*(min-nums[j])+nums[j]
|
||||||
|
* 2.min(nums[i],nums[j-1])<dp[i][j]<max(nums[i],nums[j-1])&& min=nums[j-1]
|
||||||
|
* dp[i][j]=dp[i][j-1]+(j-1-i)*(nums[j]-min)+nums[j]
|
||||||
|
* 3. min=nums[i]
|
||||||
|
* dp[i][j]=dp[i][j-1]+nums[i]
|
||||||
|
* 4.dp[i][j]>max(nums[i],nums[j-1]) && min=nums[j-1]
|
||||||
|
* dp[i][j]=dp[i][j-1]+(j-1-i)*(max-min)+max
|
||||||
|
* 3.dp初始化:dp[i][i]=0; dp[i][i+1]=min(nums[i],nums[i+1])
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* [1 8 6 2 5 4 8 3 7]
|
||||||
|
* 1 0 1 2
|
||||||
|
* 8
|
||||||
|
* 6
|
||||||
|
* 2
|
||||||
|
* 5
|
||||||
|
* 4
|
||||||
|
* 8
|
||||||
|
* 3
|
||||||
|
* 7
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxArea(int[] height) {
|
||||||
|
|
||||||
|
int[][] dp = new int[height.length][height.length];
|
||||||
|
|
||||||
|
// for (int i = 0; i < dp.length; i++) {
|
||||||
|
// dp[i][i] = 0;
|
||||||
|
// }
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
for (int j = i + 1; j < dp[0].length; j++) {
|
||||||
|
int min = Math.min(height[i], height[j - 1]);
|
||||||
|
int max = Math.max(height[i], height[j - 1]);
|
||||||
|
if (height[j] < Math.min(height[i], height[j - 1])) {
|
||||||
|
dp[i][j] = dp[i][j - 1] - (j - 1 - i) * (min - height[j]) + height[j];
|
||||||
|
} else if (min == height[i]) {
|
||||||
|
dp[i][j] = dp[i][j - 1] + height[i];
|
||||||
|
} else if (min <= height[j] && height[j] <= max) {
|
||||||
|
dp[i][j] = dp[i][j - 1] + (j - 1 - i) * (height[j] - min) + height[j];
|
||||||
|
} else {
|
||||||
|
dp[i][j] = dp[i][j - 1] + (j - 1 - i) * (max - min) + max;
|
||||||
|
}
|
||||||
|
if(result<dp[i][j]) result=dp[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int maxArea1(int[] height) {
|
||||||
|
|
||||||
|
int[] dp=new int[height.length];
|
||||||
|
|
||||||
|
// for (int i = 0; i < dp.length; i++) {
|
||||||
|
// dp[i][i] = 0;
|
||||||
|
// }
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < dp.length; i++) {
|
||||||
|
dp=new int[height.length];
|
||||||
|
for (int j = i + 1; j < dp.length; j++) {
|
||||||
|
int min = Math.min(height[i], height[j - 1]);
|
||||||
|
int max = Math.max(height[i], height[j - 1]);
|
||||||
|
if (height[j] < Math.min(height[i], height[j - 1])) {
|
||||||
|
dp[j] = dp[j - 1] - (j - 1 - i) * (min - height[j]) + height[j];
|
||||||
|
} else if (min == height[i]) {
|
||||||
|
dp[j] = dp[j - 1] + height[i];
|
||||||
|
} else if (min <= height[j] && height[j] <= max) {
|
||||||
|
dp[j] = dp[j - 1] + (j - 1 - i) * (height[j] - min) + height[j];
|
||||||
|
} else {
|
||||||
|
dp[j] = dp[j - 1] + (j - 1 - i) * (max - min) + max;
|
||||||
|
}
|
||||||
|
if (result < dp[j]) result = dp[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方双指针法,本质上还是让分别上让左右两个为界,计算雨水,得出最大值
|
||||||
|
* 速度击败59.94% 内存击败56.49% 4ms
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxArea2(int[] height) {
|
||||||
|
int l = 0, r = height.length - 1;
|
||||||
|
int ans = 0;
|
||||||
|
while (l < r) {
|
||||||
|
int area = Math.min(height[l], height[r]) * (r - l);
|
||||||
|
ans = Math.max(ans, area);
|
||||||
|
if (height[l] <= height[r]) {
|
||||||
|
++l;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
--r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:
|
||||||
|
* 本质上就是在官方题解的方法上,增加了一层while循环,减少判断次数
|
||||||
|
* 速度击败100% 内存击败29.31% 1ms
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxArea3(int[] height) {
|
||||||
|
if(height == null || height.length == 0) return 0;
|
||||||
|
int left = 0, right = height.length - 1,area = 0;
|
||||||
|
while(left < right){
|
||||||
|
if(height[left] <= height[right]){
|
||||||
|
int pre = height[left];
|
||||||
|
area = Math.max(area, pre * (right - left));
|
||||||
|
while(left < right && height[left] <= pre) left++;
|
||||||
|
}else{
|
||||||
|
int pre = height[right];
|
||||||
|
area = Math.max(area, pre * (right - left));
|
||||||
|
while(left < right && height[right] <= pre) right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-02 09:54
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣10题 正则表达式匹配:
|
||||||
|
* 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
|
||||||
|
* '.' 匹配任意单个字符
|
||||||
|
* '*' 匹配零个或多个前面的那一个元素
|
||||||
|
* 所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T10_IsMatch {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// String s = "aab", p = "c*a*b";
|
||||||
|
String s = "mississippi", p = "mis*is*ip*.";
|
||||||
|
System.out.println(isMatch(s, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
// String s = "aab", p = "c*a*b";
|
||||||
|
String s = "ab", p = ".*c";
|
||||||
|
System.out.println(isMatch(s, p));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:仍然是匹配类型的题,遍历p如果p使用完了都还没有遍历完那么就匹配不上,中途匹配不上也停止
|
||||||
|
* 尝试失败 自己的方法有太多边界条件需要处理
|
||||||
|
* @param s
|
||||||
|
* @param p
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isMatch(String s, String p) {
|
||||||
|
char[] chars = p.toCharArray();
|
||||||
|
char[] sChars = s.toCharArray();
|
||||||
|
char lastChar = '1';
|
||||||
|
int SIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < p.length(); i++) {
|
||||||
|
if (SIndex == s.length()) return true;
|
||||||
|
if (chars[i] - 'a' >= 0 && chars[i] - 'a' <= 26) {
|
||||||
|
//匹配上的是字母,判断是否能匹配s
|
||||||
|
if (i + 1 < chars.length && chars[i + 1] == '*') {
|
||||||
|
lastChar = chars[i];
|
||||||
|
} else if (sChars[SIndex++] != chars[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (chars[i] == '*') {
|
||||||
|
if (lastChar == '.') {
|
||||||
|
//之前的是‘.’
|
||||||
|
//找到下一个char中为字母的
|
||||||
|
while (i < chars.length && chars[i] - 'a' >= 0 && chars[i] - 'a' <= 26) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i == chars.length) {
|
||||||
|
return true;//因为一定能匹配上了
|
||||||
|
} else {
|
||||||
|
lastChar = chars[i];
|
||||||
|
while (SIndex < sChars.length && sChars[SIndex] != lastChar) {
|
||||||
|
SIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
//匹配上的是‘*’,for循环增加
|
||||||
|
while (SIndex < sChars.length && sChars[SIndex] == lastChar) {
|
||||||
|
SIndex++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//匹配上的是‘.’
|
||||||
|
if (i + 1 < chars.length && chars[i + 1] == '*') {
|
||||||
|
lastChar = chars[i];
|
||||||
|
} else {
|
||||||
|
SIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SIndex < sChars.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方思路:动态规划法
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: f[i][j]表示 sss 的前 i个字符与 ppp 中的前 j个字符是否能够匹配。
|
||||||
|
* 速度击败42.97% 内存击败32.92% 2ms
|
||||||
|
* @param s
|
||||||
|
* @param p
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isMatch1(String s, String p) {
|
||||||
|
int m = s.length();
|
||||||
|
int n = p.length();
|
||||||
|
|
||||||
|
boolean[][] f = new boolean[m + 1][n + 1];
|
||||||
|
f[0][0] = true;
|
||||||
|
for (int i = 0; i <= m; ++i) {
|
||||||
|
for (int j = 1; j <= n; ++j) {
|
||||||
|
if (p.charAt(j - 1) == '*') {
|
||||||
|
f[i][j] = f[i][j - 2];//不匹配字符,将该组合扔掉,不再进行匹配。
|
||||||
|
if (matches(s, p, i, j - 1)) {
|
||||||
|
f[i][j] = f[i][j] || f[i - 1][j];//能匹配 s 末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (matches(s, p, i, j)) {
|
||||||
|
f[i][j] = f[i - 1][j - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f[m][n];
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(String s, String p, int i, int j) {
|
||||||
|
if (i == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (p.charAt(j - 1) == '.') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return s.charAt(i - 1) == p.charAt(j - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-02 11:17
|
||||||
|
*@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 T11_ThreeSum {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {-1, 0, 1, 2, -1, -4};
|
||||||
|
// int[] nums = {-2,0,0,2,2};
|
||||||
|
System.out.println(threeSum(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:双指针法
|
||||||
|
* 速度击败33.39% 内存击败45.64% 33ms
|
||||||
|
* 但是和快速的方法差别不大
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<Integer>> threeSum(int[] nums) {
|
||||||
|
|
||||||
|
List<List<Integer>> result = new ArrayList<>();
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
Arrays.sort(nums);
|
||||||
|
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
if (nums[i] > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i > 0 && nums[i - 1] == nums[i]) continue;
|
||||||
|
|
||||||
|
int left = i + 1;
|
||||||
|
int right = nums.length - 1;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
sum = nums[i] + nums[left] + nums[right];
|
||||||
|
|
||||||
|
if (sum < 0) {
|
||||||
|
left++;
|
||||||
|
} else if (sum > 0) {
|
||||||
|
right--;
|
||||||
|
} else {
|
||||||
|
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
|
||||||
|
while (left < right && nums[++left] == nums[left - 1]) continue;
|
||||||
|
while (right > left && nums[right] == nums[--right]) continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-02 13:12
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣17题 电话号码的字母组合:
|
||||||
|
* 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
|
||||||
|
* 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
|
||||||
|
*
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T12_LetterCombinations {
|
||||||
|
|
||||||
|
Map<Character, List<Character>> map = new HashMap() {{
|
||||||
|
put('2', Arrays.asList('a', 'b', 'c'));
|
||||||
|
put('3', Arrays.asList('d', 'e', 'f'));
|
||||||
|
put('4', Arrays.asList('g', 'h', 'i'));
|
||||||
|
put('5', Arrays.asList('j', 'k', 'l'));
|
||||||
|
put('6', Arrays.asList('m', 'n', 'o'));
|
||||||
|
put('7', Arrays.asList('p', 'q', 'r', 's'));
|
||||||
|
put('8', Arrays.asList('t', 'u', 'v'));
|
||||||
|
put('9', Arrays.asList('w', 'x', 'y', 'z'));
|
||||||
|
}};
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<String> letterCombinations(String digits) {
|
||||||
|
if(digits.length()==0)return result;
|
||||||
|
char[] chars = digits.toCharArray();
|
||||||
|
backtracking(chars, 0);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backtracking(char[] chars, int startIndex) {
|
||||||
|
if (builder.length() == chars.length) {
|
||||||
|
result.add(new String(builder));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Character> characters = map.get(chars[startIndex]);
|
||||||
|
|
||||||
|
for (int i = 0; i < characters.size(); i++) {
|
||||||
|
builder.append(characters.get(i));
|
||||||
|
backtracking(chars, startIndex + 1);
|
||||||
|
builder.deleteCharAt(builder.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
import com.markilue.leecode.listnode.ListNodeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 10:11
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣19题 删除链表的倒数第N个节点:
|
||||||
|
* 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T13_RemoveNthFromEnd {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
ListNode root = ListNodeUtils.build(new int[]{1, 2, 3, 4, 5});
|
||||||
|
ListNode listNode = removeNthFromEnd(root, 6);
|
||||||
|
ListNodeUtils.print(listNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快慢指针:一个先走N步,然后两个指针一起走
|
||||||
|
* @param head
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode removeNthFromEnd(ListNode head, int n) {
|
||||||
|
ListNode fakeHead = new ListNode();
|
||||||
|
fakeHead.next = head;
|
||||||
|
|
||||||
|
ListNode fast = fakeHead;
|
||||||
|
ListNode slow = fakeHead;
|
||||||
|
|
||||||
|
//快指针先走n步
|
||||||
|
while (fast != null && n != 0) {
|
||||||
|
fast = fast.next;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
//n不够
|
||||||
|
if (fast == null) {
|
||||||
|
return fakeHead.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
//快指针走到头
|
||||||
|
while (fast.next != null) {
|
||||||
|
fast = fast.next;
|
||||||
|
slow = slow.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
//删除slow
|
||||||
|
slow.next = slow.next.next;
|
||||||
|
|
||||||
|
return fakeHead.next;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 10:29
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣20题 有效的括号:
|
||||||
|
* 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
|
||||||
|
* 有效字符串需满足:
|
||||||
|
* 左括号必须用相同类型的右括号闭合。
|
||||||
|
* 左括号必须以正确的顺序闭合。
|
||||||
|
* 每个右括号都有一个对应的相同类型的左括号。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T14_IsValid {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "()";
|
||||||
|
System.out.println(isValid(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:核心就是对称消除 使用stack消栈
|
||||||
|
* 速度击败48.96% 内存击败43.9% 2ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValid(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
Stack<Character> stack = new Stack<>();
|
||||||
|
Map<Character, Character> map = new HashMap() {{
|
||||||
|
put(')', '(');
|
||||||
|
put(']', '[');
|
||||||
|
put('}', '{');
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (char aChar : chars) {
|
||||||
|
if (!map.containsKey(aChar)) {
|
||||||
|
stack.push(aChar);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (stack.isEmpty() || map.get(aChar) != stack.pop()) {//这里记得将判空
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stack.isEmpty()) {//这里也要记得加判空
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isValid1(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
Deque<Character> stack = new LinkedList<>();
|
||||||
|
Map<Character, Character> map = new HashMap() {{
|
||||||
|
put(')', '(');
|
||||||
|
put(']', '[');
|
||||||
|
put('}', '{');
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (char aChar : chars) {
|
||||||
|
if (!map.containsKey(aChar)) {
|
||||||
|
stack.push(aChar);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (stack.isEmpty() || map.get(aChar) != stack.pop()) {//这里记得将判空
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stack.isEmpty()) {//这里也要记得加判空
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方优秀的解答:使用数组代替stack
|
||||||
|
* 速度击败100% 内存击败29.16% 0ms
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValid2(String s) {
|
||||||
|
if(s == null || s.length()==0){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
char[] str = s.toCharArray();
|
||||||
|
int N = str.length;
|
||||||
|
int size = 0;
|
||||||
|
char[] stack=new char[N];
|
||||||
|
for (int i=0;i<N;i++){
|
||||||
|
char cha = str[i];
|
||||||
|
if(cha=='('||cha=='['|| cha=='{'){
|
||||||
|
stack[size++] = cha =='('?')':(cha=='['?']':'}');
|
||||||
|
}else {
|
||||||
|
if(size == 0){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char last = stack[--size];
|
||||||
|
if(cha != last ){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 10:50
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣21题 合并两个有序链表:
|
||||||
|
* 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T15_MergeTwoLists {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:递归,谁小合并谁
|
||||||
|
* 速度击败100% 内存击败83% 0ms
|
||||||
|
* @param list1
|
||||||
|
* @param list2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
|
||||||
|
|
||||||
|
if (list1 == null) {
|
||||||
|
return list2;
|
||||||
|
}
|
||||||
|
if (list2 == null) {
|
||||||
|
return list1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode root = new ListNode();
|
||||||
|
if (list1.val < list2.val) {
|
||||||
|
root.val = list1.val;
|
||||||
|
root.next = mergeTwoLists(list1.next, list2);
|
||||||
|
} else {
|
||||||
|
root.val = list2.val;
|
||||||
|
root.next = mergeTwoLists(list1, list2.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public ListNode mergeKLists(ListNode[] lists) {
|
||||||
|
if(lists == null || lists.length == 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return merge(lists,0,lists.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode merge(ListNode[] lists , int left , int right){
|
||||||
|
if(left == right) {return lists[left];}
|
||||||
|
int middle = left + (right - left)/2;
|
||||||
|
ListNode list1 = merge(lists,left,middle);
|
||||||
|
ListNode list2 = merge(lists,middle + 1,right);
|
||||||
|
return mergeTwoList(list1,list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ListNode mergeTwoList(ListNode head1, ListNode head2){
|
||||||
|
ListNode dummy = new ListNode();
|
||||||
|
ListNode cur = dummy; // cur指向当前合并链表的最后一个
|
||||||
|
while(head1!=null && head2!=null){
|
||||||
|
if(head1.val <= head2.val){
|
||||||
|
cur.next = head1;
|
||||||
|
head1 = head1.next;
|
||||||
|
}else{
|
||||||
|
cur.next = head2;
|
||||||
|
head2 = head2.next;
|
||||||
|
}
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
cur.next = head1==null?head2:head1;
|
||||||
|
return dummy.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 10:56
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣22题 括号生成:
|
||||||
|
* 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T16_GenerateParenthesis {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
System.out.println(generateParenthesis(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
StringBuilder cur = new StringBuilder();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:有效的括号 实际上就是前面的(要比)要多那就是有效的括号
|
||||||
|
* 速度击败75.47% 内存击败39.67% 1ms
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> generateParenthesis(int n) {
|
||||||
|
backtracking(n, 0, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param n 需要生成多少对括号
|
||||||
|
* @param x 当前有多少个(
|
||||||
|
* @param y 当前有多少个)
|
||||||
|
*/
|
||||||
|
public void backtracking(int n, int x, int y) {
|
||||||
|
if (x < y || x > n || y > n) {
|
||||||
|
//非有效括号
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == x && n == y) {
|
||||||
|
//遍历完了,并且符合要求
|
||||||
|
result.add(new String(cur));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
cur.append('(');
|
||||||
|
backtracking(n, x + 1, y);
|
||||||
|
} else {
|
||||||
|
cur.append(')');
|
||||||
|
backtracking(n, x, y + 1);
|
||||||
|
}
|
||||||
|
cur.deleteCharAt(cur.length() - 1);//回溯
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:本质上与本人一致,只是把判断简化了
|
||||||
|
* 速度击败100% 内存击败66.13% 0ms
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> generateParenthesis1(int n) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
backTrack(result, sb, 0, 0, n);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backTrack(List<String> result, StringBuilder sb, int left, int right, int n) {
|
||||||
|
|
||||||
|
if (sb.length() == n * 2) {
|
||||||
|
result.add(sb.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left < n) {
|
||||||
|
sb.append('(');
|
||||||
|
backTrack(result, sb, left + 1, right, n);
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right < left) {
|
||||||
|
sb.append(')');
|
||||||
|
backTrack(result, sb, left, right + 1, n);
|
||||||
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
import com.markilue.leecode.listnode.ListNodeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 11:18
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣23题 合并k个升序链表:
|
||||||
|
* 给你一个链表数组,每个链表都已经按升序排列。
|
||||||
|
* 请你将所有链表合并到一个升序链表中,返回合并后的链表。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T17_MergeKLists {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
ListNode[] listNodes = new ListNode[3];
|
||||||
|
listNodes[0] = ListNodeUtils.build(new int[]{1, 4, 5});
|
||||||
|
listNodes[1] = ListNodeUtils.build(new int[]{1,3,4});
|
||||||
|
listNodes[2] = ListNodeUtils.build(new int[]{2, 6});
|
||||||
|
ListNodeUtils.print(mergeKLists(listNodes));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:二分法再合并
|
||||||
|
* 速度击败74.86% 内存击败96.99% 3ms
|
||||||
|
* @param lists
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode mergeKLists(ListNode[] lists) {
|
||||||
|
|
||||||
|
if (lists.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return split(lists, 0, lists.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode split(ListNode[] lists, int start, int end) {
|
||||||
|
if (start == end) {
|
||||||
|
return lists[start];
|
||||||
|
}
|
||||||
|
int mid = start + ((end - start) >> 1);
|
||||||
|
ListNode left = split(lists, start, mid);
|
||||||
|
ListNode right = split(lists, mid + 1, end);
|
||||||
|
|
||||||
|
return merge(left, right);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode merge(ListNode list1, ListNode list2) {
|
||||||
|
|
||||||
|
if (list1 == null) {
|
||||||
|
return list2;
|
||||||
|
}
|
||||||
|
if (list2 == null) {
|
||||||
|
return list1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode root = new ListNode();
|
||||||
|
if (list1.val < list2.val) {
|
||||||
|
root.val = list1.val;
|
||||||
|
root.next = merge(list1.next, list2);
|
||||||
|
} else {
|
||||||
|
root.val = list2.val;
|
||||||
|
root.next = merge(list1, list2.next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:同样是分治法
|
||||||
|
* 速度击败80.97% 内存击败32.68% 2ms
|
||||||
|
* @param lists
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode mergeKLists1(ListNode[] lists) {
|
||||||
|
if(lists == null || lists.length == 0){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return merge(lists,0,lists.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode merge(ListNode[] lists , int left , int right){
|
||||||
|
if(left == right) {return lists[left];}
|
||||||
|
int middle = left + (right - left)/2;
|
||||||
|
ListNode list1 = merge(lists,left,middle);
|
||||||
|
ListNode list2 = merge(lists,middle + 1,right);
|
||||||
|
return mergeTwoList(list1,list2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ListNode mergeTwoList(ListNode head1, ListNode head2){
|
||||||
|
ListNode dummy = new ListNode();
|
||||||
|
ListNode cur = dummy; // cur指向当前合并链表的最后一个
|
||||||
|
while(head1!=null && head2!=null){
|
||||||
|
if(head1.val <= head2.val){
|
||||||
|
cur.next = head1;
|
||||||
|
head1 = head1.next;
|
||||||
|
}else{
|
||||||
|
cur.next = head2;
|
||||||
|
head2 = head2.next;
|
||||||
|
}
|
||||||
|
cur = cur.next;
|
||||||
|
}
|
||||||
|
cur.next = head1==null?head2:head1;
|
||||||
|
return dummy.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-03 11:40
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣31题 下一个排列:
|
||||||
|
* 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
|
||||||
|
* 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
|
||||||
|
* 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。
|
||||||
|
* 更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,
|
||||||
|
* 那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。
|
||||||
|
* 如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
|
||||||
|
* 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
|
||||||
|
* 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
|
||||||
|
* 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
|
||||||
|
* 给你一个整数数组 nums ,找出 nums 的下一个排列。
|
||||||
|
* 必须 原地 修改,只允许使用额外常数空间。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T18_NextPermutation {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1(){
|
||||||
|
// int[] nums ={1,2,3};
|
||||||
|
int[] nums ={3,2,1};
|
||||||
|
nextPermutation(nums);
|
||||||
|
System.out.println(Arrays.toString(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:从后往前遍历,寻找第一个当前数比前一个数大的,进行交换 ; 如果找不到就进行整体的排序
|
||||||
|
* 有问题: 没有想象的那么简单
|
||||||
|
* 还有一个思路是: 先有小到大排序 通过回溯(树层去重后),找到原来的数,那么回溯的下一个值就是需要的值
|
||||||
|
* 这有一个问题就是 使用了额外的空间 ,第二是时间复杂度应该为O(2^N)
|
||||||
|
* @param nums
|
||||||
|
*/
|
||||||
|
public void nextPermutation(int[] nums) {
|
||||||
|
|
||||||
|
int i = nums.length - 1;
|
||||||
|
for (; i > 0; i--) {
|
||||||
|
if (nums[i] > nums[i - 1]) {
|
||||||
|
exchange(nums, i, i - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
quickSort(nums);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] nums={5,6,4,8,2,9,1,7,3};
|
||||||
|
quickSort(nums);
|
||||||
|
System.out.println(Arrays.toString(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void quickSort(int[] nums) {
|
||||||
|
|
||||||
|
partition(nums,0,nums.length-1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void partition(int[] nums, int i, int j) {
|
||||||
|
if (i >= j) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int mid = sort(nums, i, j);
|
||||||
|
partition(nums, i, mid - 1);//排左边
|
||||||
|
partition(nums, mid + 1, j);//排右边
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sort(int[] nums, int i, int j) {
|
||||||
|
int left = i;
|
||||||
|
int right = j+1;
|
||||||
|
int num = nums[i];
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
|
||||||
|
while (left < j && nums[++left] < num) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (0 < right && nums[--right] > num) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (left >= right) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
exchange(nums, left, right);
|
||||||
|
|
||||||
|
}
|
||||||
|
exchange(nums, right, i);
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exchange(int[] nums, int i, int j) {
|
||||||
|
int temp = nums[i];
|
||||||
|
nums[i] = nums[j];
|
||||||
|
nums[j] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方解答的思路:两边扫描,具体看笔记
|
||||||
|
* 速度击败100% 内存击败58.73% 0ms
|
||||||
|
* @param nums
|
||||||
|
*/
|
||||||
|
public void nextPermutation1(int[] nums) {
|
||||||
|
int i = nums.length - 2;
|
||||||
|
while (i >= 0 && nums[i] >= nums[i + 1]) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
if (i >= 0) {
|
||||||
|
int j = nums.length - 1;
|
||||||
|
while (j >= 0 && nums[i] >= nums[j]) {
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
swap(nums, i, j);
|
||||||
|
}
|
||||||
|
reverse(nums, i + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void swap(int[] nums, int i, int j) {
|
||||||
|
int temp = nums[i];
|
||||||
|
nums[i] = nums[j];
|
||||||
|
nums[j] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reverse(int[] nums, int start) {
|
||||||
|
int left = start, right = nums.length - 1;
|
||||||
|
while (left < right) {
|
||||||
|
swap(nums, left, right);
|
||||||
|
left++;
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-05 10:59
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣32题 最长有效括号:
|
||||||
|
* 给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T19_LongestValidParentheses {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// String s = "(())";
|
||||||
|
// String s = "()()";
|
||||||
|
// String s = "(()())";
|
||||||
|
// String s = ")(((((()())()()))()(()))(";
|
||||||
|
String s = ")())";
|
||||||
|
// String s = "()(()";
|
||||||
|
System.out.println(longestValidParentheses3(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:其实就是将有效括号 进一步变为了寻找最长,那么可以使用动态规划法
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示 以i结尾以j开头的子串 是不是有效括号
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1. s[i]匹配得上s[j]
|
||||||
|
* dp[i][j]=dp[i-1][j+1]
|
||||||
|
* 3.dp初始化: 为了初始化方便 s[i] s[j]变为s[i-1] dp[i][j]=true
|
||||||
|
* 4.dp遍历顺序:从上到下
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 暂时有问题
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestValidParentheses(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
boolean[][] dp = new boolean[chars.length][chars.length];
|
||||||
|
int maxLength = 0;
|
||||||
|
for (int i = 0; i < chars.length; i++) {
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
if (isMatch(chars, i, j)) {
|
||||||
|
if (i == j + 1) {
|
||||||
|
dp[i][j] = true;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = dp[i - 1][j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断他前后是不是也能匹配上()()的情况
|
||||||
|
if (dp[i][j]) {
|
||||||
|
int start = j;
|
||||||
|
for (int k = j - 1; k >= 0; k--) {
|
||||||
|
if (dp[j - 1][k]) {
|
||||||
|
dp[i][k] = true;//修复,对应的位置
|
||||||
|
start = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}//找起始位置
|
||||||
|
if (maxLength < i - start + 1) maxLength = i - start + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMatch(char[] chars, int i, int j) {
|
||||||
|
return chars[i] == ')' && chars[j] == '(';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仍然使用stack记录法
|
||||||
|
* 暂时有问题
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestValidParentheses1(String s) {
|
||||||
|
|
||||||
|
char[] chars = s.toCharArray();
|
||||||
|
|
||||||
|
int maxLength = 0;
|
||||||
|
int curLength = 0;
|
||||||
|
|
||||||
|
Stack<Character> stack = new Stack<>();
|
||||||
|
|
||||||
|
for (char aChar : chars) {
|
||||||
|
if (aChar == '(') {
|
||||||
|
stack.push(aChar);
|
||||||
|
} else {
|
||||||
|
if (!stack.isEmpty()) {
|
||||||
|
stack.pop();
|
||||||
|
curLength += 2;
|
||||||
|
if (maxLength < curLength) maxLength = curLength;
|
||||||
|
} else {
|
||||||
|
stack.clear();
|
||||||
|
curLength = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return maxLength;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方动态规划法:
|
||||||
|
* TODO
|
||||||
|
* 1.s[i]=‘)’ 且 s[i−1]=‘(’s[i - 1] = \text{‘(’}s[i−1]=‘(’,也就是字符串形如 “……()”“……()”“……()”,我们可以推出:
|
||||||
|
* dp[i]=dp[i−2]+2
|
||||||
|
* 2.s[i]=‘)’s[i] = \text{‘)’}s[i]=‘)’ 且 s[i−1]=‘)’s[i - 1] = \text{‘)’}s[i−1]=‘)’,也就是字符串形如 “……))”“……))”“……))”,我们可以推出: 如果 s[i−dp[i−1]−1]=‘(’s[i - \textit{dp}[i - 1] - 1] = \text{‘(’}s[i−dp[i−1]−1]=‘(’,那么
|
||||||
|
* dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestValidParentheses2(String s) {
|
||||||
|
int maxans = 0;
|
||||||
|
int[] dp = new int[s.length()];
|
||||||
|
for (int i = 1; i < s.length(); i++) {
|
||||||
|
if (s.charAt(i) == ')') {
|
||||||
|
if (s.charAt(i - 1) == '(') {
|
||||||
|
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
|
||||||
|
} else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
|
||||||
|
dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
|
||||||
|
}
|
||||||
|
maxans = Math.max(maxans, dp[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方栈法:存入栈的是位置
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestValidParentheses3(String s) {
|
||||||
|
int maxans = 0;
|
||||||
|
Deque<Integer> stack = new LinkedList<Integer>();
|
||||||
|
stack.push(-1);
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
if (s.charAt(i) == '(') {
|
||||||
|
stack.push(i);
|
||||||
|
} else {
|
||||||
|
stack.pop();
|
||||||
|
if (stack.isEmpty()) {
|
||||||
|
stack.push(i);
|
||||||
|
} else {
|
||||||
|
maxans = Math.max(maxans, i - stack.peek());//只peek不pop是关键
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-05 13:45
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣33题 搜索旋转排序数组:
|
||||||
|
* 整数数组 nums 按升序排列,数组中的值 互不相同 。
|
||||||
|
* 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,
|
||||||
|
* 使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。
|
||||||
|
* 例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
|
||||||
|
* 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
|
||||||
|
* 你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T20_Search {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {4, 5, 6, 7, 0, 1, 2};
|
||||||
|
int target = 3;
|
||||||
|
System.out.println(search(nums, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] nums = {1, 3};
|
||||||
|
int target = 3;
|
||||||
|
System.out.println(search(nums, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
int[] nums = {5,1, 3};
|
||||||
|
int target = 1;
|
||||||
|
System.out.println(search(nums, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 难度自然在于 设计一个时间复杂度为O(log n)的算法
|
||||||
|
* 时间复杂度logn则必然是考虑二分搜索 因为旋转后的数一定是小于第1个数
|
||||||
|
* 1.先找具体旋转的位置(二分法)
|
||||||
|
* 2.再分为两个区具体去找
|
||||||
|
* 速度击败100% 内存击败42.71%
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int search(int[] nums, int target) {
|
||||||
|
if (nums.length == 1) {
|
||||||
|
return nums[0] == target ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int k = searchK(nums, 0, nums.length - 1, nums[0]);
|
||||||
|
if (target < nums[0] || k == 0) {
|
||||||
|
if (target < nums[k]) return -1;
|
||||||
|
return binarySearchTrue(nums, k, nums.length - 1, target);
|
||||||
|
} else {
|
||||||
|
return binarySearchTrue(nums, 0, k - 1, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchK(int[] nums, int start, int end, int target) {
|
||||||
|
if (start == end) {
|
||||||
|
if (nums[start] < target) return start;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int mid = start + ((end - start) >> 1);
|
||||||
|
if (nums[mid] < target) {
|
||||||
|
return searchK(nums, start, mid, target);
|
||||||
|
} else {
|
||||||
|
return searchK(nums, mid + 1, end, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int binarySearchTrue(int[] nums, int start, int end, int target) {
|
||||||
|
if (start > end) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int mid = start + ((end - start) >> 1);
|
||||||
|
if (nums[mid] < target) {
|
||||||
|
return binarySearchTrue(nums, mid + 1, end, target);
|
||||||
|
} else if (nums[mid] > target) {
|
||||||
|
return binarySearchTrue(nums, start, mid - 1, target);
|
||||||
|
} else {
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方二分查找:将两次二分合并在一起;就是增加了一层判断:nums[0]和nums[mid]得到关系
|
||||||
|
* 速度击败100% 内存击败37.71%
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int search1(int[] nums, int target) {
|
||||||
|
int n = nums.length;
|
||||||
|
if (n == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (n == 1) {
|
||||||
|
return nums[0] == target ? 0 : -1;
|
||||||
|
}
|
||||||
|
int l = 0, r = n - 1;
|
||||||
|
while (l <= r) {
|
||||||
|
int mid = (l + r) / 2;
|
||||||
|
if (nums[mid] == target) {
|
||||||
|
return mid;
|
||||||
|
}
|
||||||
|
if (nums[0] <= nums[mid]) {
|
||||||
|
if (nums[0] <= target && target < nums[mid]) {
|
||||||
|
r = mid - 1;
|
||||||
|
} else {
|
||||||
|
l = mid + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (nums[mid] < target && target <= nums[n - 1]) {
|
||||||
|
l = mid + 1;
|
||||||
|
} else {
|
||||||
|
r = mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-06 10:04
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣34题 在排序数组中查找元素的第一个和最后一个位置:
|
||||||
|
* 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
|
||||||
|
* 如果数组中不存在目标值 target,返回 [-1, -1]。
|
||||||
|
* 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T21_SearchRange {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {8, 8, 8, 8, 8, 8};
|
||||||
|
int target = 8;
|
||||||
|
System.out.println(Arrays.toString(searchRange(nums, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] nums = {5, 7, 7, 8, 8, 10};
|
||||||
|
int target = 6;
|
||||||
|
System.out.println(Arrays.toString(searchRange(nums, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
int[] nums = {2, 2};
|
||||||
|
int target = 3;
|
||||||
|
System.out.println(Arrays.toString(searchRange(nums, target)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:本质上就是二分查找查找多个相同值的位置
|
||||||
|
* 速度击败100% 内存击败30.39% 0ms
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[] searchRange(int[] nums, int target) {
|
||||||
|
if (nums.length == 0) {
|
||||||
|
return new int[]{-1, -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int end = nums.length - 1;
|
||||||
|
int mid = 0;
|
||||||
|
|
||||||
|
while (start <= end) {
|
||||||
|
mid = start + ((end - start) >> 1);
|
||||||
|
|
||||||
|
if (nums[mid] < target) {
|
||||||
|
start = mid + 1;
|
||||||
|
} else if (nums[mid] > target) {
|
||||||
|
end = mid - 1;
|
||||||
|
} else {
|
||||||
|
//找到了
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start > end) {
|
||||||
|
return new int[]{-1, -1};//没找到
|
||||||
|
} else {
|
||||||
|
int left = mid;
|
||||||
|
int right = mid;
|
||||||
|
while (left > 0 && nums[left - 1] == nums[mid]) {
|
||||||
|
left--;
|
||||||
|
}
|
||||||
|
while (right < nums.length - 1 && nums[right + 1] == nums[mid]) {
|
||||||
|
right++;
|
||||||
|
}
|
||||||
|
return new int[]{left, right};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论区的简洁写法
|
||||||
|
* @param nums
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[] searchRange1(int[] nums, int target) {
|
||||||
|
if (nums.length == 0) {
|
||||||
|
return new int[]{-1, -1};
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int end = nums.length - 1;
|
||||||
|
int mid = 0;
|
||||||
|
|
||||||
|
while (start <= end) {
|
||||||
|
mid = start + ((end - start) >> 1);
|
||||||
|
|
||||||
|
if (nums[mid] == target) {
|
||||||
|
int left = mid;
|
||||||
|
int right = mid;
|
||||||
|
while (left > 0 && nums[left - 1] == nums[mid]) ++left;
|
||||||
|
while (right < nums.length - 1 && nums[right + 1] == nums[mid]) --right;
|
||||||
|
return new int[]{left, right};
|
||||||
|
} else if (nums[mid] < target) {
|
||||||
|
start = mid + 1;
|
||||||
|
}else {
|
||||||
|
end=mid-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new int[]{-1, -1};//没找到
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-06 10:29
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣39题 组合总和:
|
||||||
|
* 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
|
||||||
|
* candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
|
||||||
|
* 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T22_CombinationSum {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] candidates = {2, 3, 6, 7};
|
||||||
|
int target = 7;
|
||||||
|
System.out.println(combinationSum(candidates, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> cur = new ArrayList<>();
|
||||||
|
List<List<Integer>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回溯,注意树枝去重和树层去重方式
|
||||||
|
* 速度击败75.99% 内存击败83.16% 可能数sum没有使用回溯的方式写,稍微有点慢
|
||||||
|
* @param candidates
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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 start) {
|
||||||
|
if (sum == target) {
|
||||||
|
result.add(new ArrayList<>(cur));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = start; i < candidates.length && sum + candidates[i] <= target; i++) {//加上start树枝去重
|
||||||
|
//从这个杜绝sum>target
|
||||||
|
if (i > 0 && candidates[i] == candidates[i - 1]) {
|
||||||
|
continue;//树层去重
|
||||||
|
}
|
||||||
|
cur.add(candidates[i]);
|
||||||
|
backtracking(candidates, target, sum + candidates[i],i);
|
||||||
|
cur.remove(cur.size() - 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-06 10:48
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣42题 接雨水:
|
||||||
|
* 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T23_Trap {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
|
||||||
|
System.out.println(trap2(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:先尝试动态规划法
|
||||||
|
* 分别从两边进行动态规划,分别寻找考虑两边的最大值
|
||||||
|
* 速度击败78.21% 内存击败82.6% 1ms
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int trap(int[] height) {
|
||||||
|
|
||||||
|
int[] left = new int[height.length];
|
||||||
|
left[0] = height[0];
|
||||||
|
for (int i = 1; i < height.length; i++) {
|
||||||
|
left[i] = Math.max(height[i], left[i - 1]);//本质上就是记录从左看的最大值
|
||||||
|
}
|
||||||
|
int[] right = new int[height.length];//记录从右看的最大
|
||||||
|
right[right.length - 1] = height[height.length - 1];
|
||||||
|
for (int i = height.length - 2; i >= 0; i--) {
|
||||||
|
right[i] = Math.max(height[i], right[i + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过分别从左和右看的最大值来判断当前位置所接的雨水量
|
||||||
|
int sum = 0;
|
||||||
|
for (int i = 0; i < right.length; i++) {
|
||||||
|
//当前位置的雨水量 取决于 从左和右看中的最小值
|
||||||
|
sum += Math.min(left[i], right[i]) - height[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:先尝试双指针法
|
||||||
|
* 分别从两边进行规划指针,谁小谁往对方方向走
|
||||||
|
* TODO 本人的正确应该是纯属凑巧(不可借鉴)
|
||||||
|
* 速度击败78.21% 内存击败47.5% 1ms
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int trap1(int[] height) {
|
||||||
|
|
||||||
|
int left = 0;
|
||||||
|
int right = height.length - 1;
|
||||||
|
int leftMax = height[left];
|
||||||
|
int rightMax = height[right];
|
||||||
|
int sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < height.length; i++) {
|
||||||
|
leftMax = Math.max(height[left], leftMax);
|
||||||
|
rightMax = Math.max(height[right], rightMax);
|
||||||
|
//当前位置的雨水量取决于左右两边最小的那个
|
||||||
|
sum += Math.min(leftMax, rightMax) - height[i];
|
||||||
|
if (leftMax < rightMax) {
|
||||||
|
left++;
|
||||||
|
} else {
|
||||||
|
right--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方的左右指针法:浅优化(每次循环少判断一次)
|
||||||
|
* @param height
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int trap2(int[] height) {
|
||||||
|
int ans = 0;
|
||||||
|
int left = 0, right = height.length - 1;
|
||||||
|
int leftMax = 0, rightMax = 0;
|
||||||
|
while (left < right) {
|
||||||
|
leftMax = Math.max(leftMax, height[left]);
|
||||||
|
rightMax = Math.max(rightMax, height[right]);
|
||||||
|
if (height[left] < height[right]) {
|
||||||
|
//如果左边的小,那么左边的水一定可以蓄起来,那么就计算左边的水
|
||||||
|
ans += leftMax - height[left];
|
||||||
|
++left;
|
||||||
|
} else {
|
||||||
|
ans += rightMax - height[right];
|
||||||
|
--right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-06 11:34
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣46题 全排列:
|
||||||
|
* 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T24_Permute {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {1, 2, 3};
|
||||||
|
System.out.println(permute(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Integer> cur = new ArrayList<>();
|
||||||
|
List<List<Integer>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心就是需要知道哪些位置用过,哪些位置没有用过
|
||||||
|
* 速度击败100% 内存击败69.64% 1ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<Integer>> permute(int[] nums) {
|
||||||
|
|
||||||
|
boolean[] used = new boolean[nums.length];
|
||||||
|
backtracking(nums, used);
|
||||||
|
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);
|
||||||
|
used[i] = false;
|
||||||
|
cur.remove(cur.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-06 11:42
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣48题 旋转图像:
|
||||||
|
* 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
|
||||||
|
* 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T25_Rotate {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[][] matrix = {{5,1,9,11}, {2,4,8,10}, {13,3,6,7},{15,14,12,16}};
|
||||||
|
rotate(matrix);
|
||||||
|
for (int[] ints : matrix) {
|
||||||
|
System.out.println(Arrays.toString(ints));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:这题跟螺旋数组那题很像:都是寻找循环不变量
|
||||||
|
* 速度击败100% 内存击败67.68% 0ms
|
||||||
|
* @param matrix
|
||||||
|
*/
|
||||||
|
public void rotate(int[][] matrix) {
|
||||||
|
|
||||||
|
//i为层
|
||||||
|
for (int i = 0; i < matrix.length / 2; i++) {
|
||||||
|
int row = i;//row方向移动
|
||||||
|
int col = i;//col方向移动
|
||||||
|
|
||||||
|
int row1 = i;//需要复制的row方向
|
||||||
|
int col1 = matrix.length - 1 - i;//需要复制的col方向
|
||||||
|
int[] last = new int[matrix.length - 1 - i];//记录上次的位置
|
||||||
|
|
||||||
|
for (int index = 0; col < matrix.length - 1 - i; col++, row1++) {
|
||||||
|
last[index++] = matrix[row1][col1];
|
||||||
|
matrix[row1][col1] = matrix[row][col];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = 0; col1 > i; col1--) {
|
||||||
|
int temp = matrix[row1][col1];
|
||||||
|
matrix[row1][col1] = last[index];
|
||||||
|
last[index++] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = 0; row1 > i; row1--) {
|
||||||
|
int temp = matrix[row1][col1];
|
||||||
|
matrix[row1][col1] = last[index];
|
||||||
|
last[index++] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int index = 0; col1 < matrix.length - 1 - i; col1++) {
|
||||||
|
matrix[row1][col1] = last[index++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.swing.event.ListDataEvent;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-07 10:17
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣49题 字母异位词分组:
|
||||||
|
* 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
|
||||||
|
* 字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T26_GroupAnagrams {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String[] str={"eat", "tea", "tan", "ate", "nat", "bat"};
|
||||||
|
// String[] str = {""};
|
||||||
|
// String[] str = {"ac","c"};
|
||||||
|
System.out.println(groupAnagrams(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<List<String>> result = new ArrayList<>();
|
||||||
|
List<String> cur = new ArrayList<>();
|
||||||
|
Map<Character, Integer> map = new HashMap<>();
|
||||||
|
Map<Integer,Map<Character, Integer>> bigMap=new HashMap<>();
|
||||||
|
|
||||||
|
//TODO 超出时间限制,第111个无法通过;修改后使用bigmap只在空的时候记录,通过 :速度击败5% 内存击败5%
|
||||||
|
public List<List<String>> groupAnagrams(String[] strs) {
|
||||||
|
boolean[] used = new boolean[strs.length];//记录这个单词是否被使用过
|
||||||
|
for (int i = 0; i < strs.length; i++) {
|
||||||
|
if (!used[i]) {
|
||||||
|
used[i] = true;
|
||||||
|
addMap(strs[i],map,i);
|
||||||
|
cur.add(strs[i]);
|
||||||
|
for (int j = i + 1; j < strs.length; j++) {
|
||||||
|
if (!used[j] && isAnagrams(strs[j],j)) {
|
||||||
|
cur.add(strs[j]);
|
||||||
|
used[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.add(new ArrayList<>(cur));
|
||||||
|
cur.clear();
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMap(String str,Map<Character,Integer> map,int index) {
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
char key = str.charAt(i);
|
||||||
|
map.put(key, map.getOrDefault(key, 0) + 1);
|
||||||
|
}
|
||||||
|
bigMap.put(index, map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnagrams(String curStr,int index) {
|
||||||
|
Map<Character, Integer> curMap = bigMap.getOrDefault(index, new HashMap<>());
|
||||||
|
|
||||||
|
if(curMap.isEmpty()){
|
||||||
|
addMap(curStr,curMap,index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curMap.equals(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方计数法:由于互为字母异位词的两个字符串包含的字母相同,
|
||||||
|
* 因此两个字符串中的相同字母出现的次数一定是相同的
|
||||||
|
* 故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。
|
||||||
|
* 速度击败26.63% 内存击败22.41% 10ms
|
||||||
|
* @param strs
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<String>> groupAnagrams1(String[] strs) {
|
||||||
|
Map<String, List<String>> map = new HashMap<String, List<String>>();
|
||||||
|
for (String str : strs) {
|
||||||
|
int[] counts = new int[26];
|
||||||
|
int length = str.length();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
counts[str.charAt(i) - 'a']++;
|
||||||
|
}
|
||||||
|
// 将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
|
if (counts[i] != 0) {
|
||||||
|
sb.append((char) ('a' + i));
|
||||||
|
sb.append(counts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String key = sb.toString();
|
||||||
|
List<String> list = map.getOrDefault(key, new ArrayList<String>());
|
||||||
|
list.add(str);
|
||||||
|
map.put(key, list);
|
||||||
|
}
|
||||||
|
return new ArrayList<List<String>>(map.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方合理且最快:通过排序建立key
|
||||||
|
* 速度击败80.36% 内存击败57.98% 6ms
|
||||||
|
* @param strs
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<String>> groupAnagrams2(String[] strs) {
|
||||||
|
Map<String, List<String>> map = new HashMap<>();
|
||||||
|
for (String str : strs) {
|
||||||
|
char[] cs = str.toCharArray();
|
||||||
|
Arrays.sort(cs);
|
||||||
|
String key = String.valueOf(cs);
|
||||||
|
List<String> list = map.getOrDefault(key, new ArrayList<>());
|
||||||
|
list.add(str);
|
||||||
|
map.put(key, list);
|
||||||
|
}
|
||||||
|
return new ArrayList(map.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-07 11:38
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣53题 最大子数组和:
|
||||||
|
* 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
|
||||||
|
* 子数组 是数组中的一个连续部分。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T27_MaxSubArray {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] nums={-2,-1};
|
||||||
|
System.out.println(maxSubArray1(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贪心:要尽可能多的数字,如果等于0了直接抛弃
|
||||||
|
* 速度击败100% 内存击败79.27%
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray(int[] nums) {
|
||||||
|
int cur = 0;
|
||||||
|
int maxValue = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
cur += nums[i];
|
||||||
|
if (maxValue < cur) maxValue = cur;
|
||||||
|
if (cur < 0) cur = 0;
|
||||||
|
}
|
||||||
|
return maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态规划: dp定义为使用nums[0-i]的最大子数组和
|
||||||
|
* 速度击败2.9% 内存击败5.9% 15ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray1(int[] nums) {
|
||||||
|
// if(nums.length==0)return nums[0];
|
||||||
|
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(dp[i - 1][1] + nums[i], nums[i]);//要之前,还是不要之前
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp[dp.length-1][0],dp[dp.length-1][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态规划: 滚动数组优化
|
||||||
|
* 速度击败100% 内存击败68.67% 1ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxSubArray2(int[] nums) {
|
||||||
|
// if(nums.length==0)return nums[0];
|
||||||
|
|
||||||
|
int dp0 = Integer.MIN_VALUE;//至少要一个
|
||||||
|
int dp1 = nums[0];//至少要一个
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
|
||||||
|
dp0 = Math.max(dp0, dp1);//现在就不要,全不要
|
||||||
|
dp1 = Math.max(dp1 + nums[i], nums[i]);//要之前,还是不要之前
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp0,dp1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-07 13:14
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣55题 跳跃游戏:
|
||||||
|
* 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
|
||||||
|
* 数组中的每个元素代表你在该位置可以跳跃的最大长度。
|
||||||
|
* 判断你是否能够到达最后一个下标。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T28_CanJump {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:贪心,记录一个能到的最大索引
|
||||||
|
* 速度击败94.51% 内存击败97.5% 2ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean canJump(int[] nums) {
|
||||||
|
|
||||||
|
if(nums.length==1)return true;
|
||||||
|
|
||||||
|
int maxIndex = 0;
|
||||||
|
int curIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < nums.length - 1 && i <= maxIndex; i++) {
|
||||||
|
curIndex = i + nums[i];
|
||||||
|
if (curIndex > maxIndex) maxIndex = curIndex;
|
||||||
|
if (maxIndex >= nums.length - 1) return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-07 13:24
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣56题 合并区间:
|
||||||
|
* 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。
|
||||||
|
* 请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T29_Merge {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据start排序,如果end<start则有重叠区间 7ms
|
||||||
|
* @param intervals
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[][] merge(int[][] intervals) {
|
||||||
|
|
||||||
|
Arrays.sort(intervals, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
|
||||||
|
List<int[]> list = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int[] interval : intervals) {
|
||||||
|
if (list.isEmpty() || list.get(list.size() - 1)[1] < interval[0]) {
|
||||||
|
list.add(interval);
|
||||||
|
} else {
|
||||||
|
int length = list.size() - 1;
|
||||||
|
int[] last = list.get(length);
|
||||||
|
last[1] = Math.max(last[1], interval[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.toArray(new int[0][0]);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方较快解法:6ms
|
||||||
|
* @param intervals
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[][] merge1(int[][] intervals) {
|
||||||
|
//按xstart排序
|
||||||
|
Arrays.sort(intervals, new Comparator<int[]>() {
|
||||||
|
@Override
|
||||||
|
public int compare(int[] o1, int[] o2) {
|
||||||
|
//优化 但是需要警惕超过int范围
|
||||||
|
return o1[0] - o2[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//寻找重叠区间,并进行合并
|
||||||
|
int length=0;//记录新数组的长度
|
||||||
|
int[][] result =new int[intervals.length][2];
|
||||||
|
result[0]=intervals[0];
|
||||||
|
int lastEnd=intervals[0][1];
|
||||||
|
|
||||||
|
for (int i = 1; i < intervals.length; i++) {
|
||||||
|
if(lastEnd>=intervals[i][0]){
|
||||||
|
//重叠区间,需要合并
|
||||||
|
lastEnd=Math.max(lastEnd,intervals[i][1]);
|
||||||
|
result[length][1]=lastEnd;
|
||||||
|
}else {
|
||||||
|
//非重叠区间,直接添加
|
||||||
|
length++;
|
||||||
|
result[length]=intervals[i];
|
||||||
|
lastEnd=intervals[i][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//缩短至result的真实长度
|
||||||
|
return Arrays.copyOf(result,length+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-07 13:40
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣62题 不同路径:
|
||||||
|
* 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
|
||||||
|
* 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
|
||||||
|
* 问总共有多少条不同的路径?
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T30_UniquePaths {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划法 ->当前的位置可以是上面往下走一步,也可以是左边往右边走一步
|
||||||
|
* 速度击败100% 内存击败16.37% 0ms
|
||||||
|
* @param m
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int uniquePaths(int m, int n) {
|
||||||
|
|
||||||
|
int[][] dp = new int[m][n];
|
||||||
|
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
dp[i][0] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
dp[0][i] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < m; i++) {
|
||||||
|
for (int j = 1; j < n; j++) {
|
||||||
|
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m - 1][n - 1];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-08 09:56
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣64题 最小路径和:
|
||||||
|
* 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
|
||||||
|
* 说明:每次只能向下或者向右移动一步。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T31_MinPathSum {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态规划法:
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示到达i,j的最小路径和
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j]
|
||||||
|
* 3.dp初始化: dp[0][0]=grid[0][0];
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败94.76% 内存击败47.9% 2ms
|
||||||
|
* @param grid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int minPathSum(int[][] grid) {
|
||||||
|
int m = grid.length;
|
||||||
|
int n = grid[0].length;
|
||||||
|
|
||||||
|
int[][] dp = new int[m][n];
|
||||||
|
dp[0][0] = grid[0][0];
|
||||||
|
|
||||||
|
for (int i = 1; i < m; i++) {
|
||||||
|
dp[i][0] = dp[i - 1][0] + grid[i][0];
|
||||||
|
}
|
||||||
|
for (int i = 1; i < n; i++) {
|
||||||
|
dp[0][i] = dp[0][i - 1] + grid[0][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < m; i++) {
|
||||||
|
for (int j = 1; j < n; j++) {
|
||||||
|
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[m - 1][n - 1];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int[][] memo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:回溯+记忆化搜索
|
||||||
|
* 速度击败100% 内存击败19.31% 0ms
|
||||||
|
* @param grid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int minPathSum1(int[][] grid) {
|
||||||
|
int m = grid.length;
|
||||||
|
int n = grid[0].length;
|
||||||
|
|
||||||
|
memo = new int[m][n];
|
||||||
|
for (int[] row : memo)
|
||||||
|
Arrays.fill(row, -1);
|
||||||
|
|
||||||
|
return dp(grid,m-1,n-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int dp(int[][] grid,int i,int j){
|
||||||
|
if(i == 0 && j == 0){
|
||||||
|
return grid[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i <0 || j<0){
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
if(memo[i][j] != -1){
|
||||||
|
return memo[i][j];
|
||||||
|
}else{
|
||||||
|
memo[i][j] = Math.min(dp(grid,i-1,j),dp(grid,i,j-1)) + grid[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
return memo[i][j];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-08 10:13
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣70题 爬楼梯:
|
||||||
|
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
|
||||||
|
* 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T32_ClimbStairs {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:缩维dp,当前位置可以从上一个位置和上两个位置爬到
|
||||||
|
* 速度击败100% 内存击败43.59%
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int climbStairs(int n) {
|
||||||
|
if (n < 2) return 1;
|
||||||
|
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
dp[0] = 1;
|
||||||
|
dp[1] = 1;
|
||||||
|
|
||||||
|
for (int i = 2; i < dp.length; i++) {
|
||||||
|
dp[i] = dp[i - 1] + dp[i - 2];
|
||||||
|
}
|
||||||
|
return dp[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:通用动态规划法 ->爬几楼都行
|
||||||
|
* 速度击败100% 内存击败79.7%
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int climbStairs1(int n) {
|
||||||
|
if (n < 2) return 1;
|
||||||
|
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
dp[0] = 1;
|
||||||
|
dp[1] = 1;
|
||||||
|
int[] step={1,2};//可以爬一楼或者两楼
|
||||||
|
|
||||||
|
for (int i = 2; i < dp.length; i++) {
|
||||||
|
for (int j = 0; j < step.length; j++) {
|
||||||
|
dp[i] += dp[i - step[j]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-08 10:33
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣72题 编辑距离:
|
||||||
|
* 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
|
||||||
|
* 你可以对一个单词进行如下三种操作:
|
||||||
|
* 插入一个字符
|
||||||
|
* 删除一个字符
|
||||||
|
* 替换一个字符
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T33_MinDistance {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:
|
||||||
|
* TODO dp五部曲:
|
||||||
|
* 1.dp定义: dp[i][j]表示word1[0-i]变到word2[0-j]需要多少步
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.word1[i]==word1[j] 看看剩下的需要多少步
|
||||||
|
* dp[i][j]=dp[i-1][j-1]
|
||||||
|
* 2.word1[i]!=word1[j] 看看增加,或者删除,修改能否到达
|
||||||
|
* dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1
|
||||||
|
* 3.dp初始化:dp[0][j]=j;dp[i][0]=i
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:
|
||||||
|
* 速度击败87.48% 内存击败12.47% 4ms
|
||||||
|
* @param word1
|
||||||
|
* @param word2
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int minDistance(String word1, String word2) {
|
||||||
|
int length1 = word1.length();
|
||||||
|
int length2 = word2.length();
|
||||||
|
|
||||||
|
int[][] dp = new int[length1 + 1][length2 + 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(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dp[length1][length2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:回溯+记忆化搜搜
|
||||||
|
* 速度击败100% 内存击败5.1% 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){
|
||||||
|
return j+1;
|
||||||
|
}
|
||||||
|
if(j==-1){
|
||||||
|
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,102 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-08 10:48
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣75题 颜色分类:
|
||||||
|
* 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
|
||||||
|
* 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
|
||||||
|
* 必须在不使用库内置的 sort 函数的情况下解决这个问题。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T34_SortColors {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] nums ={1};
|
||||||
|
sortColors(nums);
|
||||||
|
System.out.println(Arrays.toString(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 桶子法:三个桶分别装0 1 2
|
||||||
|
* 速度击败100% 内存击败43.28% 0ms
|
||||||
|
* @param nums
|
||||||
|
*/
|
||||||
|
public void sortColors(int[] nums) {
|
||||||
|
|
||||||
|
int[] zero = new int[nums.length];
|
||||||
|
int[] one = new int[nums.length];
|
||||||
|
int[] two = new int[nums.length];
|
||||||
|
int zeroIndex = 0;
|
||||||
|
int oneIndex = 0;
|
||||||
|
int twoIndex = 0;
|
||||||
|
|
||||||
|
//遍历nums,放进桶里
|
||||||
|
for (int i = 0; i < nums.length; i++) {
|
||||||
|
if (nums[i] == 0) {
|
||||||
|
zero[zeroIndex++] = nums[i];
|
||||||
|
} else if (nums[i] == 1) {
|
||||||
|
one[oneIndex++] = nums[i];
|
||||||
|
} else {
|
||||||
|
two[twoIndex++] = nums[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int resultIndex = 0;
|
||||||
|
|
||||||
|
//把0放回来
|
||||||
|
for (int i = 0; i < zeroIndex; i++) {
|
||||||
|
nums[resultIndex++] = zero[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
//把1放回来
|
||||||
|
for (int i = 0; i < oneIndex; i++) {
|
||||||
|
nums[resultIndex++] = one[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
//把2放回来
|
||||||
|
for (int i = 0; i < twoIndex; i++) {
|
||||||
|
nums[resultIndex++] = two[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解:双指针法:遇上0或者1就跟前面的交换
|
||||||
|
* TODO 使用的是常数空间
|
||||||
|
* 速度击败100% 内存击败45.18% 0ms
|
||||||
|
* @param nums
|
||||||
|
*/
|
||||||
|
public void sortColors1(int[] nums) {
|
||||||
|
int n = nums.length;
|
||||||
|
int p0 = 0, p1 = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (nums[i] == 1) {
|
||||||
|
int temp = nums[i];
|
||||||
|
nums[i] = nums[p1];
|
||||||
|
nums[p1] = temp;
|
||||||
|
++p1;
|
||||||
|
} else if (nums[i] == 0) {
|
||||||
|
int temp = nums[i];
|
||||||
|
nums[i] = nums[p0];
|
||||||
|
nums[p0] = temp;
|
||||||
|
if (p0 < p1) {
|
||||||
|
temp = nums[i];
|
||||||
|
nums[i] = nums[p1];
|
||||||
|
nums[p1] = temp;
|
||||||
|
}
|
||||||
|
++p0;
|
||||||
|
++p1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
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.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-08 11:19
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣76题 最小覆盖子串:
|
||||||
|
* 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
|
||||||
|
* 注意:
|
||||||
|
* 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
|
||||||
|
* 如果 s 中存在这样的子串,我们保证它是唯一的答案。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T35_MinWindow {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "ADOBECODEBANC";
|
||||||
|
String t = "ABC";
|
||||||
|
System.out.println(minWindow2(s, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:试试贪心
|
||||||
|
* 先使用map记录一下t的字母及个数
|
||||||
|
* 分别在s里面找最后一次出现的下标
|
||||||
|
* 暂时有问题 :理论上来说思路是对的,但是到后面,需要寻找一个最短区间会比较麻烦
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String minWindow(String s, String t) {
|
||||||
|
|
||||||
|
Map<Character, Integer> map = new HashMap<>();//<char,count>
|
||||||
|
|
||||||
|
//记录每个出现的次数
|
||||||
|
for (int i = 0; i < t.length(); i++) {
|
||||||
|
char c = t.charAt(i);
|
||||||
|
map.put(c, map.getOrDefault(c, 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<List<Integer>, Integer> list = new HashMap<>();//记录各个字母出现的位置
|
||||||
|
//寻找每个字母在s中出现的次数
|
||||||
|
for (Map.Entry<Character, Integer> entry : map.entrySet()) {
|
||||||
|
Character key = entry.getKey();
|
||||||
|
Integer count = entry.getValue();
|
||||||
|
int start = -1;
|
||||||
|
ArrayList<Integer> charList = new ArrayList<>();
|
||||||
|
while (true) {
|
||||||
|
start = s.indexOf(key, start + 1);//找到了就从他后面找
|
||||||
|
if (start == -1) break;//没找到直接返回
|
||||||
|
else charList.add(start);
|
||||||
|
}
|
||||||
|
list.put(charList, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
//list [[0,10]->1,[3,9]->1,[5,12]->1]
|
||||||
|
int min = 0;
|
||||||
|
int max = 0;
|
||||||
|
//在list里面寻找最小和最大的值
|
||||||
|
for (Map.Entry<List<Integer>, Integer> listIntegerEntry : list.entrySet()) {
|
||||||
|
List<Integer> list1 = listIntegerEntry.getKey();
|
||||||
|
Integer count = listIntegerEntry.getValue();
|
||||||
|
if (list1.size() < count) return "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return s.substring(min, max + 1);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//题解
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论区模板题解:
|
||||||
|
* right扩容找范围,left缩容找最优
|
||||||
|
* 速度击败63.87% 内存击败25.95% 14ms
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String minWindow1(String s, String t) {
|
||||||
|
//1.维护两个map记录窗口中的符合条件的字符以及need的字符
|
||||||
|
Map<Character, Integer> window = new HashMap<>();
|
||||||
|
Map<Character, Integer> need = new HashMap<>();//need中存储的是需要的字符以及需要的对应的数量
|
||||||
|
for (char c : t.toCharArray())
|
||||||
|
need.put(c, need.getOrDefault(c, 0) + 1);
|
||||||
|
int left = 0, right = 0;//双指针
|
||||||
|
int count = 0;//count记录当前窗口中符合need要求的字符的数量,当count == need.size()时即可shrik窗口
|
||||||
|
int start = 0;//start表示符合最优解的substring的起始位序
|
||||||
|
int len = Integer.MAX_VALUE;//len用来记录最终窗口的长度,并且以len作比较,淘汰选出最小的substring的len
|
||||||
|
|
||||||
|
//一次遍历找“可行解”
|
||||||
|
while (right < s.length()) {
|
||||||
|
//更新窗口
|
||||||
|
char c = s.charAt(right);
|
||||||
|
right++;//窗口扩大
|
||||||
|
// window.put(c,window.getOrDefault(c,0)+1);其实并不需要将s中所有的都加入windowsmap,只需要将need中的加入即可
|
||||||
|
if (need.containsKey(c)) {
|
||||||
|
window.put(c, window.getOrDefault(c, 0) + 1);
|
||||||
|
if (need.get(c).equals(window.get(c))) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//System.out.println****Debug位置
|
||||||
|
//shrink左边界,找符合条件的最优解
|
||||||
|
while (count == need.size()) {
|
||||||
|
if (right - left < len) {//不断“打擂”找满足条件的len最短值,并记录最短的子串的起始位序start
|
||||||
|
len = right - left;
|
||||||
|
start = left;
|
||||||
|
}
|
||||||
|
//更新窗口——这段代码逻辑几乎完全同上面的更新窗口
|
||||||
|
char d = s.charAt(left);
|
||||||
|
left++;//窗口缩小
|
||||||
|
if (need.containsKey(d)) {
|
||||||
|
//window.put(d,window.get(d)-1);——bug:若一进去就将window对应的键值缩小,就永远不会满足下面的if,while也会一直执行,知道left越界,因此,尽管和上面对窗口的处理几乎一样,但是这个处理的顺序还是很关键的!要细心!
|
||||||
|
if (need.get(d).equals(window.get(d))) {
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
window.put(d, window.get(d) - 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自己写一遍模板题解:
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String minWindow2(String s, String t) {
|
||||||
|
Map<Character, Integer> need = new HashMap<>();//记录t的需求
|
||||||
|
HashMap<Character, Integer> window = new HashMap<>();//记录窗口中实际存储
|
||||||
|
int left = 0;
|
||||||
|
int right = 0;//左右指针,维护窗口
|
||||||
|
int count = 0;//记录凑够need里面的char的个数;如果满了就证明window合适,开始缩容
|
||||||
|
int len = Integer.MAX_VALUE;//记录最短长度
|
||||||
|
int curLen;
|
||||||
|
int start=0;
|
||||||
|
|
||||||
|
//遍历t,记录need
|
||||||
|
for (int i = 0; i < t.length(); i++) {
|
||||||
|
char c = t.charAt(i);
|
||||||
|
need.put(c, need.getOrDefault(c, 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//左右指针开始遍历
|
||||||
|
while (right < s.length()) {
|
||||||
|
char c = s.charAt(right++);
|
||||||
|
|
||||||
|
//判断判断是不是需要的
|
||||||
|
if (need.containsKey(c)) {
|
||||||
|
window.put(c, window.getOrDefault(c, 0) + 1);//增加窗口中的个数
|
||||||
|
if (window.get(c).equals(need.get(c))) {
|
||||||
|
count++;//数量相等了count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断是否已经到达容量,若到达则开始缩容
|
||||||
|
while (count == need.size()) {
|
||||||
|
curLen = right - left;
|
||||||
|
if (curLen < len) {
|
||||||
|
len = curLen;
|
||||||
|
start=left;
|
||||||
|
}
|
||||||
|
//缩容
|
||||||
|
char c1 = s.charAt(left++);
|
||||||
|
if (need.containsKey(c1)) {
|
||||||
|
if (window.get(c1).equals(need.get(c1))) {
|
||||||
|
count--;//数量相等了count--;
|
||||||
|
}
|
||||||
|
window.put(c1, window.getOrDefault(c1, 0) - 1);//增加窗口中的个数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:使用count[]数组代替map,本质上类似,也是r扩容,l缩容
|
||||||
|
* 速度击败99.34% 内存击败80.92%
|
||||||
|
* @param s
|
||||||
|
* @param t
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String minWindow3(String s, String t) {
|
||||||
|
int sLen = s.length();
|
||||||
|
int tLen = t.length();
|
||||||
|
if(sLen < tLen) return "";
|
||||||
|
|
||||||
|
int[] cnt = new int[128];
|
||||||
|
for(int i = 0; i < tLen; i++){
|
||||||
|
cnt[s.charAt(i)]--;
|
||||||
|
cnt[t.charAt(i)]++;
|
||||||
|
}
|
||||||
|
int differ = 0;
|
||||||
|
for(int i = 0; i < cnt.length; i++){
|
||||||
|
if(cnt[i] > 0) differ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(differ == 0) return s.substring(0, tLen);
|
||||||
|
|
||||||
|
int size = Integer.MAX_VALUE;
|
||||||
|
int start = -1;
|
||||||
|
int l = 0, r = tLen;
|
||||||
|
|
||||||
|
while(r < sLen){
|
||||||
|
while(r < sLen && differ > 0){
|
||||||
|
if(cnt[s.charAt(r)] == 1){//等于1则证明之前刚好差一个,直接删了
|
||||||
|
differ--;
|
||||||
|
}
|
||||||
|
cnt[s.charAt(r++)]--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(differ == 0){
|
||||||
|
if(r - l < size){
|
||||||
|
size = r - l;
|
||||||
|
start = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cnt[s.charAt(l)] == 0){//等于0则证明之前刚刚好
|
||||||
|
differ++;
|
||||||
|
}
|
||||||
|
cnt[s.charAt(l++)]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return start == -1 ? "" : s.substring(start, start + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-09 10:18
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣78题 子集:
|
||||||
|
* 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
|
||||||
|
* 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T36_Subsets {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {1, 2, 3};
|
||||||
|
System.out.println(subsets(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
List<Integer> cur = new ArrayList<>();
|
||||||
|
List<List<Integer>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<List<Integer>> subsets(int[] nums) {
|
||||||
|
backtracking(nums, 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backtracking(int[] nums, int start) {
|
||||||
|
if (nums.length == start) {
|
||||||
|
result.add(new ArrayList<>(cur));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
if(i==0){
|
||||||
|
backtracking(nums, start + 1);
|
||||||
|
}else {
|
||||||
|
cur.add(nums[start]);
|
||||||
|
backtracking(nums, start + 1);
|
||||||
|
cur.remove(cur.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-09 10:37
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣79题 单词搜索:
|
||||||
|
* 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
|
||||||
|
* 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T37_Exist {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
char[][] board = {{'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'}};
|
||||||
|
String word = "ABCCED";
|
||||||
|
System.out.println(exist(board, word));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
char[][] board = {{'A', 'B', 'C', 'E'}, {'S', 'F', 'C', 'S'}, {'A', 'D', 'E', 'E'}};
|
||||||
|
String word = "SEE";
|
||||||
|
System.out.println(exist(board, word));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
char[][] board = {{'a'}};
|
||||||
|
String word = "a";
|
||||||
|
System.out.println(exist(board, word));
|
||||||
|
}
|
||||||
|
StringBuilder cur = new StringBuilder();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:回溯:开头加for是因为需要以每一个当开头都试一下,比价麻烦
|
||||||
|
* 速度击败14.16% 内存击败57.17% 271ms
|
||||||
|
* @param board
|
||||||
|
* @param word
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean exist(char[][] board, String word) {
|
||||||
|
|
||||||
|
for (int i = 0; i < board.length; i++) {
|
||||||
|
for (int j = 0; j < board[0].length; j++) {
|
||||||
|
if(backtracking(board, word, i, j, new boolean[board.length][board[0].length])){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean backtracking(char[][] board, String word, int i, int j, boolean[][] used) {
|
||||||
|
if (cur.length() == word.length()) {
|
||||||
|
if (word.equals(cur.toString())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (i >= board.length || j >= board[0].length || i < 0 || j < 0||used[i][j]) {
|
||||||
|
//索引越界 或者 被用过
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (board[i][j] != word.charAt(cur.length())) {//只有相等才继续
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cur.append(board[i][j]);
|
||||||
|
used[i][j] = true;
|
||||||
|
for (int k = 0; k < 4; k++) {
|
||||||
|
|
||||||
|
boolean flag = false;
|
||||||
|
if (k == 0) {
|
||||||
|
flag = backtracking(board, word, i, j + 1, used);
|
||||||
|
} else if (k == 1) {
|
||||||
|
flag = backtracking(board, word, i + 1, j, used);
|
||||||
|
} else if (k == 2) {
|
||||||
|
flag = backtracking(board, word, i, j - 1, used);
|
||||||
|
} else {
|
||||||
|
flag = backtracking(board, word, i - 1, j, used);
|
||||||
|
}
|
||||||
|
if (flag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used[i][j] = false;
|
||||||
|
cur.deleteCharAt(cur.length() - 1);
|
||||||
|
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:使用了很多优化手段
|
||||||
|
* 不用使用cur去记录了,而是直接使用index来判断当前比对到哪里了
|
||||||
|
* 从而避免了大量的回溯;同时也不再使用for循环,而是全在if里面
|
||||||
|
* 速度击败100% 内存击败52.42% 0ms
|
||||||
|
* @param board
|
||||||
|
* @param word
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean exist1(char[][] board, String word) {
|
||||||
|
|
||||||
|
boolean[][]used = new boolean[board.length][board[0].length];
|
||||||
|
char[] chars = word.toCharArray();
|
||||||
|
// dfs优化加速:如果满足头部字符比较多,那就反转wordArray,从尾部开始dfs
|
||||||
|
int len = chars.length;
|
||||||
|
int head = 0;
|
||||||
|
|
||||||
|
//判断等于头部的多还是尾部的多
|
||||||
|
for (char[] row : board) {
|
||||||
|
for (char ch : row) {
|
||||||
|
if (ch == chars[0]) {
|
||||||
|
head++;
|
||||||
|
} else if (ch == chars[len - 1]) {
|
||||||
|
head--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//头多就交换为尾多
|
||||||
|
if (head > 0) {
|
||||||
|
// reverse
|
||||||
|
int l = 0;
|
||||||
|
int r = len - 1;
|
||||||
|
while (l < r) {
|
||||||
|
char temp = chars[r];
|
||||||
|
chars[r] = chars[l];
|
||||||
|
chars[l] = temp;
|
||||||
|
l++;
|
||||||
|
r--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < board.length; i++) {
|
||||||
|
for (int j = 0; j < board[0].length; j++) {
|
||||||
|
if (exist(i, j, 0, board, chars, used)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exist(int i, int j, int index,char[][] board, char[] word, boolean[][]used) {
|
||||||
|
if (i == board.length || j == board[0].length || i < 0 || j < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (board[i][j] == word[index] && !used[i][j]) {
|
||||||
|
//这里传参index去避免回溯
|
||||||
|
if (++ index == word.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
used[i][j] = true;
|
||||||
|
if (exist(i, j + 1, index, board, word, used)
|
||||||
|
|| exist(i + 1, j, index, board, word, used)
|
||||||
|
|| exist(i, j - 1, index, board, word, used)
|
||||||
|
|| exist(i - 1, j, index, board, word, used)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
used[i][j] = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-10 09:57
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣84题 柱状图中最大的矩形:
|
||||||
|
* 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
|
||||||
|
* 求在该柱状图中,能够勾勒出来的矩形的最大面积。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T38_LargestRectangleArea {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] height = {2, 1, 5, 6, 2, 3};
|
||||||
|
System.out.println(largestRectangleArea(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] height = {9, 0};
|
||||||
|
System.out.println(largestRectangleArea(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test2() {
|
||||||
|
int[] height = {6,7,5,2,4,5,9,3};
|
||||||
|
System.out.println(largestRectangleArea1(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:感觉和接雨水很像,只不过一个是要找总量比他少的
|
||||||
|
* 尝试使用双指针法
|
||||||
|
* 尚且有问题
|
||||||
|
* @param heights
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int largestRectangleArea(int[] heights) {
|
||||||
|
if (heights.length == 1) {
|
||||||
|
return heights[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = heights.length >> 1;
|
||||||
|
int right = left;
|
||||||
|
int leftMin = Integer.MAX_VALUE;
|
||||||
|
int rightMin = Integer.MAX_VALUE;
|
||||||
|
int maxArea = 0;
|
||||||
|
int curArea = 0;
|
||||||
|
|
||||||
|
while (left >= 0 && right < heights.length) {
|
||||||
|
leftMin = Math.min(heights[left], leftMin);
|
||||||
|
rightMin = Math.min(heights[right], rightMin);
|
||||||
|
curArea = Math.min(leftMin, rightMin) * (right - left + 1);
|
||||||
|
if (maxArea < curArea) maxArea = curArea;
|
||||||
|
if (left == 0 || heights[left] < heights[right]) {//矮的人放心左移
|
||||||
|
right++;
|
||||||
|
} else {
|
||||||
|
left--;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//忽略了每个单一的情况
|
||||||
|
for (int i = 0; i < heights.length; i++) {
|
||||||
|
if (heights[i] > maxArea) maxArea = heights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxArea;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO 官方单调栈解法:具体可以看笔记
|
||||||
|
* 1)首先我们枚举某一根柱子 iii 作为高 h=heights[i]
|
||||||
|
* 2)随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于 h。
|
||||||
|
* 换句话说,我们需要找到左右两侧最近的高度小于 h 的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于 h,
|
||||||
|
* 并且就是 i能够扩展到的最远范围。
|
||||||
|
* 本质上就是寻找两边单调最大的值
|
||||||
|
* 速度击败44.82% 内存击败9.7% 30ms
|
||||||
|
* TDOO 本质上就是记录以当前高为基础,最多能扩展左右到哪里,再用当前高*最多扩展
|
||||||
|
* @param heights
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int largestRectangleArea1(int[] heights) {
|
||||||
|
int n = heights.length;
|
||||||
|
int[] left = new int[n];
|
||||||
|
int[] right = new int[n];
|
||||||
|
|
||||||
|
Deque<Integer> mono_stack = new ArrayDeque<Integer>();
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
|
||||||
|
mono_stack.pop();
|
||||||
|
}
|
||||||
|
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
|
||||||
|
mono_stack.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
mono_stack.clear();
|
||||||
|
for (int i = n - 1; i >= 0; --i) {
|
||||||
|
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
|
||||||
|
mono_stack.pop();
|
||||||
|
}
|
||||||
|
right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
|
||||||
|
mono_stack.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ans = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快,常数空间优化
|
||||||
|
* 速度击败100% 内存击败85.54% 4ms
|
||||||
|
* @param heights
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int largestRectangleArea2(int[] heights) {
|
||||||
|
int len = heights.length;
|
||||||
|
int[] index = new int[len + 1];
|
||||||
|
int[] stack = new int[len + 1];
|
||||||
|
int top = 0;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
while (top > 0 && stack[top] > heights[i]) {
|
||||||
|
int height = stack[top];
|
||||||
|
while (height == stack[--top]) {
|
||||||
|
}
|
||||||
|
result = Math.max(result, height * (i - index[top]));
|
||||||
|
}
|
||||||
|
stack[++top] = heights[i];
|
||||||
|
index[top] = i + 1;
|
||||||
|
}
|
||||||
|
while (stack[top] > 0) {
|
||||||
|
int height = stack[top];
|
||||||
|
while (height == stack[--top]) {
|
||||||
|
}
|
||||||
|
result = Math.max(result, height * (len - index[top]));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-10 13:43
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣85题 最大矩形:
|
||||||
|
* 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T39_MaximalRectangle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:当前位置能不能和其他地方连起来取决于 左一块是不是1和上1块是不是1
|
||||||
|
* TODO 官方解法 : 本质上于T38类似,可以使用单调栈解法 就是在T38的基础上再加了一个列级的for循环
|
||||||
|
* 速度击败48.24% 内存击败77.7% 16ms
|
||||||
|
* @param matrix
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maximalRectangle(char[][] matrix) {
|
||||||
|
int m = matrix.length;
|
||||||
|
if (m == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int n = matrix[0].length;
|
||||||
|
int[][] left = new int[m][n];
|
||||||
|
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
for (int j = 0; j < n; j++) {
|
||||||
|
if (matrix[i][j] == '1') {
|
||||||
|
left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
for (int j = 0; j < n; j++) { // 对于每一列,使用基于柱状图的方法
|
||||||
|
int[] up = new int[m];
|
||||||
|
int[] down = new int[m];
|
||||||
|
|
||||||
|
Deque<Integer> stack = new LinkedList<Integer>();
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
up[i] = stack.isEmpty() ? -1 : stack.peek();
|
||||||
|
stack.push(i);
|
||||||
|
}
|
||||||
|
stack.clear();
|
||||||
|
for (int i = m - 1; i >= 0; i--) {
|
||||||
|
while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) {
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
down[i] = stack.isEmpty() ? m : stack.peek();
|
||||||
|
stack.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < m; i++) {
|
||||||
|
int height = down[i] - up[i] - 1;
|
||||||
|
int area = height * left[i][j];
|
||||||
|
ret = Math.max(ret, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:本质上与上面的一致,不知道为啥快
|
||||||
|
* 速度击败100% 内存击败55.97% 1ms
|
||||||
|
* @param matrix
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maximalRectangle1(char[][] matrix) {
|
||||||
|
int[] heights = new int[matrix[0].length];
|
||||||
|
int max = 0;
|
||||||
|
for (int i = 0; i < matrix.length; i++) {
|
||||||
|
updateHeights(heights, matrix[i]);
|
||||||
|
|
||||||
|
max = Math.max(max, largestRectangleArea(heights));
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int largestRectangleArea(int[] heights) {
|
||||||
|
int len = heights.length;
|
||||||
|
int max = 0;
|
||||||
|
int[] stack = new int[len];
|
||||||
|
int k = -1;
|
||||||
|
for (int r = 0; r < len; r++) {
|
||||||
|
while (k != -1 && heights[stack[k]] > heights[r]) {
|
||||||
|
int low = stack[k--];
|
||||||
|
int l = k == -1 ? -1 : stack[k];
|
||||||
|
max = Math.max(max, (r - 1 - l) * heights[low]);
|
||||||
|
}
|
||||||
|
stack[++k] = r;
|
||||||
|
}
|
||||||
|
while (k != -1) {
|
||||||
|
int low = stack[k--];
|
||||||
|
int l = k == -1 ? -1 : stack[k];
|
||||||
|
max = Math.max(max, (len - 1 - l) * heights[low]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateHeights(int[] heights, char[] cells) {
|
||||||
|
for (int j = 0; j < heights.length; j++) {
|
||||||
|
heights[j] = cells[j] == '0' ? 0 : heights[j] + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
import com.markilue.leecode.tree.TreeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-10 14:35
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣94题 二叉树的中序遍历:
|
||||||
|
* 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T40_inorderTraversal {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
|
||||||
|
TreeNode root = TreeUtils.structureTree(Arrays.asList(1, null, 2,null,null, 3), 0);
|
||||||
|
System.out.println(inorderTraversal1(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:stack栈法
|
||||||
|
* 速度击败100% 内存击败20.99% 0ms
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Integer> inorderTraversal(TreeNode root) {
|
||||||
|
|
||||||
|
Stack<TreeNode> stack = new Stack<>();
|
||||||
|
|
||||||
|
TreeNode cur = root;
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
|
||||||
|
while (cur != null || !stack.isEmpty()) {
|
||||||
|
if (cur != null) {
|
||||||
|
stack.push(cur);
|
||||||
|
cur = cur.left;
|
||||||
|
} else {
|
||||||
|
TreeNode node = stack.pop();
|
||||||
|
result.add(node.val);
|
||||||
|
cur = node.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* morris遍历
|
||||||
|
* 速度击败100% 内存击败21.68%
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Integer> inorderTraversal1(TreeNode root) {
|
||||||
|
|
||||||
|
TreeNode cur = root;
|
||||||
|
List<Integer> result = new ArrayList<>();
|
||||||
|
|
||||||
|
while (cur != null) {
|
||||||
|
if (cur.left != null) {
|
||||||
|
TreeNode left = cur.left;
|
||||||
|
|
||||||
|
//寻找他左节点的最右节点
|
||||||
|
while (left.right != null && left.right != cur) {
|
||||||
|
left = left.right;
|
||||||
|
}
|
||||||
|
|
||||||
|
//判断是从什么条件出来的
|
||||||
|
if (left.right == null) {
|
||||||
|
left.right = cur;
|
||||||
|
//放心将cur左移
|
||||||
|
cur = cur.left;
|
||||||
|
} else {
|
||||||
|
//第二次遍历到了,可以加入
|
||||||
|
result.add(cur.val);
|
||||||
|
cur = cur.right;
|
||||||
|
left.right = null;//防止下次再来
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//第二次遍历到了,可以加入
|
||||||
|
result.add(cur.val);
|
||||||
|
cur = cur.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-10 15:07
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣96题 不同的二叉搜索树:
|
||||||
|
* 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T41_NumTrees {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
System.out.println(numTrees(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: n等于几 就是分别代表使用1-n为根节点的情况之后
|
||||||
|
* 速度击败100% 内存击败93.62% 0ms
|
||||||
|
* @param n
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numTrees(int n) {
|
||||||
|
if (n < 3) return n;
|
||||||
|
|
||||||
|
int[] dp = new int[n + 1];
|
||||||
|
dp[0] = 1;
|
||||||
|
dp[1] = 1;
|
||||||
|
|
||||||
|
for (int i = 3; i < dp.length; i++) {
|
||||||
|
//分别以1为底-i为底之和
|
||||||
|
for (int j = 1; j <= i; j++) {
|
||||||
|
dp[i] += dp[j - 1] * dp[i - j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[n];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
import com.markilue.leecode.tree.TreeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-10 15:20
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣98题 验证二叉搜索树:
|
||||||
|
* 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
|
||||||
|
* 有效 二叉搜索树定义如下:
|
||||||
|
* 节点的左子树只包含 小于 当前节点的数。
|
||||||
|
* 节点的右子树只包含 大于 当前节点的数。
|
||||||
|
* 所有左子树和右子树自身必须也是二叉搜索树。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T42_IsValidBST {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
// TreeNode root = TreeUtils.structureTree(Arrays.asList(5, 1, 4, null, null, 3, 6), 0);
|
||||||
|
TreeNode root = TreeUtils.structureTree(Arrays.asList(2,1,3), 0);
|
||||||
|
System.out.println(isValidBST(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:深度优先,从下往上确定范围
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isValidBST(TreeNode root) {
|
||||||
|
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isValidBST(TreeNode root, long leftThreshold, long rightThreshold) {
|
||||||
|
if (root == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean left = isValidBST(root.left, leftThreshold, root.val);
|
||||||
|
if (!left) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean right = isValidBST(root.right, root.val, rightThreshold);
|
||||||
|
|
||||||
|
if (!right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.val > leftThreshold && root.val < rightThreshold;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test4(){
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
int count = sc.nextInt();
|
||||||
|
|
||||||
|
while(count-->0){
|
||||||
|
int a = sc.nextInt();
|
||||||
|
int b = sc.nextInt();
|
||||||
|
System.out.println(a+b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// int count = sc.nextInt();
|
||||||
|
String[] s = sc.nextLine().split(" ");
|
||||||
|
int[] a=new int[s.length];
|
||||||
|
|
||||||
|
int res = Arrays.stream(s).mapToInt(Integer::parseInt).sum();
|
||||||
|
|
||||||
|
System.out.println(res);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 09:53
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣101题 对称二叉树:
|
||||||
|
* 给你一个二叉树的根节点 root , 检查它是否轴对称。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T43_IsSymmetric {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归法:速度击败100% 内存击败43.52%
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isSymmetric(TreeNode root) {
|
||||||
|
return isSymmetric(root.left, root.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSymmetric(TreeNode root1, TreeNode root2) {
|
||||||
|
if (root1 == null && root2 == null) {
|
||||||
|
return true;
|
||||||
|
} else if (root1 == null) {
|
||||||
|
return false;
|
||||||
|
} else if (root2 == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root1.val != root2.val) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSymmetric(root1.left, root2.right) && isSymmetric(root1.right, root2.left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 10:00
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣102题 二叉树的层序遍历:
|
||||||
|
* 给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T44_LevelOrder {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 队列迭代法:
|
||||||
|
* 速度击败100% 内存击败72.7% 0ms
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<Integer>> levelOrder(TreeNode root) {
|
||||||
|
|
||||||
|
List<List<Integer>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
if (root == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue<TreeNode> queue = new LinkedList<>();
|
||||||
|
|
||||||
|
queue.add(root);
|
||||||
|
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
int size = queue.size();
|
||||||
|
List<Integer> level = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
TreeNode node = queue.poll();
|
||||||
|
level.add(node.val);
|
||||||
|
if (node.left != null) queue.add(node.left);
|
||||||
|
if (node.right != null) queue.add(node.right);
|
||||||
|
}
|
||||||
|
result.add(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
List<List<Integer>> result=new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dfs:
|
||||||
|
* 速度击败100% 内存击败65.56% 0ms
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<List<Integer>> levelOrder1(TreeNode root) {
|
||||||
|
level(root,0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void level(TreeNode root,int level){
|
||||||
|
if(root==null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(level==result.size()){
|
||||||
|
result.add(new ArrayList<>());
|
||||||
|
}
|
||||||
|
//这个level以前处理过,加入在原来的level位置
|
||||||
|
result.get(level).add(root.val);
|
||||||
|
level(root.left,level+1);
|
||||||
|
level(root.right,level+1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 10:12
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣104题 二叉树的最大深度:
|
||||||
|
* 给定一个二叉树,找出其最大深度。
|
||||||
|
* 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
|
||||||
|
* 说明: 叶子节点是指没有子节点的节点。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T45_MaxDepth {
|
||||||
|
|
||||||
|
|
||||||
|
int maxDepth = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:队列法也可以,这里使用dfs
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxDepth(TreeNode root) {
|
||||||
|
if (root == null) return 0;
|
||||||
|
dfs(root, 1);
|
||||||
|
return maxDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void dfs(TreeNode root, int level) {
|
||||||
|
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxDepth < level) {
|
||||||
|
maxDepth = level;
|
||||||
|
}
|
||||||
|
dfs(root.left, level + 1);
|
||||||
|
dfs(root.right, level + 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
import com.markilue.leecode.tree.TreeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 10:21
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣105题 从前序与中序遍历序列构造二叉树:
|
||||||
|
* 给定两个整数数组 preorder 和 inorder
|
||||||
|
* 其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历
|
||||||
|
* 请构造二叉树并返回其根节点。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T46_BuildTree {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] preorder = {3, 9, 20, 15, 7};
|
||||||
|
int[] inorder = {9, 3, 15, 20, 7};
|
||||||
|
TreeUtils.printTreeByLevel(buildTree(preorder, inorder));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] preorder = {1, 2};
|
||||||
|
int[] inorder = {2, 1};
|
||||||
|
TreeUtils.printTreeByLevel(buildTree(preorder, inorder));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:根据前序遍历的root来分割中序遍历
|
||||||
|
* 速度击败14.83% 内存击败54.64% 6ms
|
||||||
|
* @param preorder
|
||||||
|
* @param inorder
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public TreeNode buildTree(int[] preorder, int[] inorder) {
|
||||||
|
return buildTree(preorder, inorder, 0, preorder.length - 1, 0, inorder.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TreeNode buildTree(int[] preorder, int[] inorder, int preLeft, int preRight, int inLeft, int inRight) {
|
||||||
|
if (preLeft > preRight || inLeft > inRight) return null;
|
||||||
|
TreeNode root = new TreeNode(preorder[preLeft]);
|
||||||
|
if (preLeft == preRight) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
//在inorder里面找root对应的索引,分割左右子树
|
||||||
|
int index = findIndex(inorder, inLeft, inRight, preorder[preLeft]);
|
||||||
|
|
||||||
|
//构造root的左子树
|
||||||
|
root.left = buildTree(preorder, inorder, preLeft + 1, preLeft + (index - inLeft), inLeft, index - 1);
|
||||||
|
|
||||||
|
|
||||||
|
//构造root的右子树
|
||||||
|
root.right = buildTree(preorder, inorder, preLeft + (index - inLeft) + 1, preRight, index + 1, inRight);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findIndex(int[] inorder, int inLeft, int inRight, int value) {
|
||||||
|
|
||||||
|
for (int i = inLeft; i <= inRight; i++) {
|
||||||
|
if (inorder[i] == value) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
import com.markilue.leecode.tree.TreeUtils;
|
||||||
|
import com.sun.xml.internal.bind.v2.model.core.EnumLeafInfo;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 10:50
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣114题 二叉树展开为链表:
|
||||||
|
* 给你二叉树的根结点 root ,请你将它展开为一个单链表:
|
||||||
|
*
|
||||||
|
* 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
|
||||||
|
* 展开后的单链表应该与二叉树 先序遍历 顺序相同。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T47_Flatten {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
TreeNode treeNode = TreeUtils.structureTree(Arrays.asList(1, 2, 5, 3, 4, null, 6), 0);
|
||||||
|
getResult(treeNode);
|
||||||
|
TreeUtils.printTreeByLevel(treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
TreeNode treeNode = TreeUtils.structureTree(Arrays.asList(1, null,2,null,null,3), 0);
|
||||||
|
getResult(treeNode);
|
||||||
|
TreeUtils.printTreeByLevel(treeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路: 稍微纠结了一会,本来想着直接前序遍历记录下来,在赋值给result,但是不行
|
||||||
|
* TODO 后来的思路: 展平左边,展平右边,将左边的右边设置为当前的右边,再把当前root的right设置有左边,再把左边删除
|
||||||
|
* 速度击败100% 内存击败85.56%
|
||||||
|
* @param root
|
||||||
|
*/
|
||||||
|
public void flatten(TreeNode root) {
|
||||||
|
getResult(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode getResult(TreeNode root) {
|
||||||
|
if (root == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeNode node = getResult(root.left);
|
||||||
|
TreeNode temp=node;
|
||||||
|
while (node != null && node.right != null) {
|
||||||
|
node = node.right;
|
||||||
|
}
|
||||||
|
//有左子树就放
|
||||||
|
if (node != null) {
|
||||||
|
node.right = getResult(root.right);
|
||||||
|
root.right = temp;
|
||||||
|
root.left = null;
|
||||||
|
}else {
|
||||||
|
//TODO 需要注意:没有也就是单独调一下右边
|
||||||
|
getResult(root.right);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解:记录前驱节点
|
||||||
|
* 非常的精妙,思路上可以借鉴morris遍历
|
||||||
|
* 速度击败1005 内存击败86.16%
|
||||||
|
* @param root
|
||||||
|
*/
|
||||||
|
public void flatten1(TreeNode root) {
|
||||||
|
TreeNode curr = root;
|
||||||
|
while (curr != null) {
|
||||||
|
if (curr.left != null) {
|
||||||
|
TreeNode next = curr.left;
|
||||||
|
TreeNode predecessor = next;
|
||||||
|
//找到左节点的最右节点,他就是左节点前序遍历的最后一个,将他的右边设置为curr.right即可
|
||||||
|
while (predecessor.right != null) {
|
||||||
|
predecessor = predecessor.right;
|
||||||
|
}
|
||||||
|
predecessor.right = curr.right;
|
||||||
|
curr.left = null;
|
||||||
|
curr.right = next;
|
||||||
|
}
|
||||||
|
curr = curr.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 11:33
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣121 买卖股票的最佳时机:
|
||||||
|
* 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
|
||||||
|
* 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
|
||||||
|
* 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T48_MaxProfit {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int[] prices={7,1,5,3,6,4};
|
||||||
|
System.out.println(maxProfit1(prices));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:实施贪心
|
||||||
|
* 速度击败100% 内存击败56.4% 1ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit(int[] prices) {
|
||||||
|
|
||||||
|
int minPrice = prices[0];
|
||||||
|
int curProfit = 0;
|
||||||
|
int maxProfit = 0;
|
||||||
|
|
||||||
|
for (int price : prices) {
|
||||||
|
curProfit = price - minPrice;
|
||||||
|
if (maxProfit < curProfit) maxProfit = curProfit;
|
||||||
|
if (minPrice > price) minPrice = price;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxProfit;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划
|
||||||
|
*
|
||||||
|
* 速度击败16.79% 内存击败93.59% 23ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit1(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], -prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[prices.length-1][0];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动数组优化
|
||||||
|
* 速度击败55.23% 内存击败23.81% 2ms
|
||||||
|
* @param prices
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProfit2(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, -prices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.tree.TreeNode;
|
||||||
|
import com.markilue.leecode.tree.TreeUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-11 11:47
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣124题 二叉树中的最大路径和:
|
||||||
|
* 路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。
|
||||||
|
* 同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
|
||||||
|
* 路径和 是路径中各节点值的总和。
|
||||||
|
* 给你一个二叉树的根节点 root ,返回其 最大路径和 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T49_MaxPathSum {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
TreeNode root = TreeUtils.structureTree(Arrays.asList(-10, 9, 20, null, null, 15, 7), 0);
|
||||||
|
// System.out.println(maxPathSum1(root, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int maxSum = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:本质上就是判断左节点之和 和当前值 来判断左端要不要
|
||||||
|
* 先中序遍历dfs
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxPathSum(TreeNode root) {
|
||||||
|
|
||||||
|
if (root == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
//遍历左子树决定当前节点要不要
|
||||||
|
int leftSum = maxPathSum(root.left);
|
||||||
|
int total = leftSum + root.val;
|
||||||
|
|
||||||
|
return total > 0 ? total + maxPathSum(root.right) : maxPathSum(root.right);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:状态可以分为要当前节点(加两边)和不要当前节点(加一边),两边都不要
|
||||||
|
* 有问题,想不清楚
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int[] maxPathSum1(TreeNode root) {
|
||||||
|
if (root == null) {
|
||||||
|
return new int[]{0, 0, 0};//<zero,one,two>
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] left = maxPathSum1(root);
|
||||||
|
int[] right = maxPathSum1(root);
|
||||||
|
int zero = Math.max(0, root.val);
|
||||||
|
|
||||||
|
int one = Math.max(Math.max(left[0],right[0])+root.val,Math.max(left[1],right[1])+ root.val);
|
||||||
|
int two = left[0] + right[0] + root.val;
|
||||||
|
|
||||||
|
return new int[]{zero,one,two};
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解:
|
||||||
|
* 递归
|
||||||
|
* @param root
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxPathSum2(TreeNode root) {
|
||||||
|
maxGain(root);
|
||||||
|
return maxSum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int maxGain(TreeNode node) {
|
||||||
|
if (node == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归计算左右子节点的最大贡献值
|
||||||
|
// 只有在最大贡献值大于 0 时,才会选取对应子节点
|
||||||
|
int leftGain = Math.max(maxGain(node.left), 0);
|
||||||
|
int rightGain = Math.max(maxGain(node.right), 0);
|
||||||
|
|
||||||
|
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
|
||||||
|
int priceNewpath = node.val + leftGain + rightGain;
|
||||||
|
|
||||||
|
// 更新答案
|
||||||
|
maxSum = Math.max(maxSum, priceNewpath);//全要的对比
|
||||||
|
|
||||||
|
// 返回节点的最大贡献值
|
||||||
|
return node.val + Math.max(leftGain, rightGain);//只要一边的返回
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-12 10:58
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣128题 最长连续序列:
|
||||||
|
* 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
|
||||||
|
* 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T50_LongestConsecutive {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {};
|
||||||
|
System.out.println(longestConsecutive(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:排序后连续,但是时间复杂度O(n)
|
||||||
|
* 速度击败99.86% 内存击败91.74% 11ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestConsecutive(int[] nums) {
|
||||||
|
if (nums == null || nums.length == 0) return 0;
|
||||||
|
|
||||||
|
Arrays.sort(nums);
|
||||||
|
|
||||||
|
int max = 1;
|
||||||
|
int cur = 1;
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
if (nums[i] == nums[i - 1] + 1) {
|
||||||
|
cur++;
|
||||||
|
} else if (nums[i] == nums[i - 1]) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
cur = 1;
|
||||||
|
}
|
||||||
|
if (cur > max) max = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解:哈希表 时间复杂度O(n)
|
||||||
|
* 速度击败78.83% 内存击败28.14% 19ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int longestConsecutive1(int[] nums) {
|
||||||
|
Set<Integer> num_set = new HashSet<Integer>();
|
||||||
|
for (int num : nums) {
|
||||||
|
num_set.add(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
int longestStreak = 0;
|
||||||
|
|
||||||
|
for (int num : num_set) {
|
||||||
|
if (!num_set.contains(num - 1)) {
|
||||||
|
int currentNum = num;
|
||||||
|
int currentStreak = 1;
|
||||||
|
|
||||||
|
while (num_set.contains(currentNum + 1)) {
|
||||||
|
currentNum += 1;
|
||||||
|
currentStreak += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
longestStreak = Math.max(longestStreak, currentStreak);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return longestStreak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-12 11:25
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣136题 只出现一次的数字:
|
||||||
|
* 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
|
||||||
|
* 你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T51_SingleNumber {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:使用hashset去重,最后还在hashset中的就是剩下的
|
||||||
|
* 速度击败19.56% 内存击败46.55% 10ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int singleNumber(int[] nums) {
|
||||||
|
|
||||||
|
Set<Integer> set = new HashSet<>();
|
||||||
|
|
||||||
|
for (int num : nums) {
|
||||||
|
if (set.contains(num)) {
|
||||||
|
set.remove(num);
|
||||||
|
} else {
|
||||||
|
set.add(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int result = 0;
|
||||||
|
for (Integer integer : set) {
|
||||||
|
result = integer;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方思路:位运算去重
|
||||||
|
* 速度击败99.99% 内存击败44.28% 1ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int singleNumber1(int[] nums) {
|
||||||
|
|
||||||
|
int singleNumber = 0;
|
||||||
|
for (int num : nums) {
|
||||||
|
singleNumber = singleNumber ^ num;
|
||||||
|
}
|
||||||
|
return singleNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-12 11:44
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣139题 单词拆分:
|
||||||
|
* 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
|
||||||
|
* 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T52_WordBreak {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
String s = "leetcode";
|
||||||
|
List<String> dict = Arrays.asList("leet", "code");
|
||||||
|
System.out.println(wordBreak(s, dict));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划法,依次判断当前位置能否使用word拼接出来
|
||||||
|
* 速度击败88.38% 内存击败89.96%
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak(String s, List<String> wordDict) {
|
||||||
|
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
dp[0] = true;
|
||||||
|
for (int i = 1; i < dp.length; i++) {
|
||||||
|
for (int j = 0; j < wordDict.size(); j++) {
|
||||||
|
String word = wordDict.get(j);
|
||||||
|
int length = word.length();
|
||||||
|
if (i >= length) dp[i] |= dp[i - length] && word.equals(s.substring(i - length, i));
|
||||||
|
if (dp[i]) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s.length()];
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以前的一些写法:本质上类似,但是if变简单了,不需要次次都判断了,直接dp[i]=true
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak1(String s, List<String> wordDict) {
|
||||||
|
boolean[] dp = new boolean[s.length() + 1];
|
||||||
|
dp[0] = true;
|
||||||
|
|
||||||
|
for (int i = 1; i <= s.length(); i++) {
|
||||||
|
for (String word : wordDict) {
|
||||||
|
int len = word.length();
|
||||||
|
if (i >= len && dp[i - len] && word.equals(s.substring(i - len, i))) {
|
||||||
|
dp[i] = true;//
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[s.length()];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:回溯+记忆化搜索
|
||||||
|
* @param s
|
||||||
|
* @param wordDict
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean wordBreak2(String s, List<String> wordDict) {
|
||||||
|
int[] memo = new int[s.length()];
|
||||||
|
Arrays.fill(memo, -1);
|
||||||
|
return check(s, 0, wordDict, memo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean check(String s, int idx, List<String> wordDict, int[] memo) {
|
||||||
|
if (idx == s.length()) return true;
|
||||||
|
if (memo[idx] != -1) return memo[idx] != 0;//记忆化回溯
|
||||||
|
for (String word : wordDict)
|
||||||
|
if (s.startsWith(word, idx) && check(s, idx + word.length(), wordDict, memo)) {//找到了,就check后面行不行
|
||||||
|
memo[idx] = 1;//找到了
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//没找到
|
||||||
|
memo[idx] = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-12 12:20
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣141 环形链表:
|
||||||
|
* 给你一个链表的头节点 head ,判断链表中是否有环。
|
||||||
|
* 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
|
||||||
|
* 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
|
||||||
|
* 注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
|
||||||
|
* 如果链表中存在环 ,则返回 true 。 否则,返回 false 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T53_HasCycle {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:简单题 快慢指针 一个走一步,一个走两步,如果有环则一定相遇
|
||||||
|
* 速度击败100% 内存击败74.13% 0ms
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean hasCycle(ListNode head) {
|
||||||
|
|
||||||
|
ListNode fast = head;
|
||||||
|
ListNode slow = head;
|
||||||
|
|
||||||
|
while (fast != null && fast.next != null) {
|
||||||
|
fast = fast.next.next;
|
||||||
|
slow = slow.next;
|
||||||
|
if (fast == slow) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-12 12:34
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣142题 环形链表II:
|
||||||
|
* 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
|
||||||
|
* 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
|
||||||
|
* 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
|
||||||
|
* 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
|
||||||
|
* 不允许修改 链表。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T54_DetectCycle {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:快慢指针法,先相遇,再让他走x步和从头出发的相遇
|
||||||
|
* 速度击败100% 内存击败68.41%
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode detectCycle(ListNode head) {
|
||||||
|
|
||||||
|
ListNode fast = head;
|
||||||
|
ListNode slow = head;
|
||||||
|
|
||||||
|
while (fast != null && fast.next != null) {
|
||||||
|
//有环则一定不会等于null
|
||||||
|
fast = fast.next.next;
|
||||||
|
slow = slow.next;
|
||||||
|
|
||||||
|
if (fast == slow) {
|
||||||
|
//两者相遇了
|
||||||
|
//从头出发一个,从相遇位置出发一个
|
||||||
|
ListNode start = head;
|
||||||
|
ListNode meet = fast;
|
||||||
|
while (start != meet) {
|
||||||
|
start = start.next;
|
||||||
|
meet = meet.next;
|
||||||
|
}
|
||||||
|
return meet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;//没有相遇
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.omg.CORBA.PUBLIC_MEMBER;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-13 17:13
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣146题 LRU缓存:
|
||||||
|
* 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
|
||||||
|
* 实现 LRUCache 类:
|
||||||
|
* 1)LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
|
||||||
|
* 2)int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
|
||||||
|
* 3)void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;
|
||||||
|
* 如果不存在,则向缓存中插入该组 key-value 。
|
||||||
|
* 如果插入操作导致关键字数量超过 capacity
|
||||||
|
* 则应该 逐出 最久未使用的关键字。
|
||||||
|
* 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T55_LRUCache {
|
||||||
|
|
||||||
|
private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
|
||||||
|
private int size;
|
||||||
|
private int capacity;
|
||||||
|
private DLinkedNode head, tail;
|
||||||
|
|
||||||
|
|
||||||
|
class DLinkedNode {
|
||||||
|
int key;
|
||||||
|
int value;
|
||||||
|
DLinkedNode prev;
|
||||||
|
DLinkedNode next;
|
||||||
|
public DLinkedNode() {}
|
||||||
|
public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public T55_LRUCache() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public T55_LRUCache(int capacity) {
|
||||||
|
this.size = 0;
|
||||||
|
this.capacity = capacity;
|
||||||
|
// 使用伪头部和伪尾部节点
|
||||||
|
head = new DLinkedNode();
|
||||||
|
tail = new DLinkedNode();
|
||||||
|
head.next = tail;
|
||||||
|
tail.prev = head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int key) {
|
||||||
|
|
||||||
|
DLinkedNode node = cache.get(key);
|
||||||
|
if (node == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// 如果 key 存在,先通过哈希表定位,再移到头部
|
||||||
|
moveToHead(node);
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(int key, int value) {
|
||||||
|
|
||||||
|
DLinkedNode node = cache.get(key);
|
||||||
|
if (node == null) {
|
||||||
|
// 如果 key 不存在,创建一个新的节点
|
||||||
|
DLinkedNode newNode = new DLinkedNode(key, value);
|
||||||
|
// 添加进哈希表
|
||||||
|
cache.put(key, newNode);
|
||||||
|
// 添加至双向链表的头部
|
||||||
|
addToHead(newNode);
|
||||||
|
++size;
|
||||||
|
if (size > capacity) {
|
||||||
|
// 如果超出容量,删除双向链表的尾部节点
|
||||||
|
DLinkedNode tail = removeTail();
|
||||||
|
// 删除哈希表中对应的项
|
||||||
|
cache.remove(tail.key);
|
||||||
|
--size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
|
||||||
|
node.value = value;
|
||||||
|
moveToHead(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToHead(DLinkedNode node) {
|
||||||
|
node.prev = head;
|
||||||
|
node.next = head.next;
|
||||||
|
head.next.prev = node;
|
||||||
|
head.next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeNode(DLinkedNode node) {
|
||||||
|
node.prev.next = node.next;
|
||||||
|
node.next.prev = node.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveToHead(DLinkedNode node) {
|
||||||
|
removeNode(node);
|
||||||
|
addToHead(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DLinkedNode removeTail() {
|
||||||
|
DLinkedNode res = tail.prev;
|
||||||
|
removeNode(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
T55_LRUCache lRUCache = new T55_LRUCache(2);
|
||||||
|
lRUCache.put(1, 1); // 缓存是 {1=1}
|
||||||
|
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
|
||||||
|
System.out.println(lRUCache.get(1)); // 返回 1
|
||||||
|
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
|
||||||
|
System.out.println(lRUCache.get(2)); // 返回 -1 (未找到)
|
||||||
|
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
|
||||||
|
System.out.println(lRUCache.get(1)); // 返回 -1 (未找到)
|
||||||
|
System.out.println(lRUCache.get(3)); // 返回 3
|
||||||
|
System.out.println(lRUCache.get(4)); // 返回 4
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
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.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-14 09:59
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣148题 排序链表:
|
||||||
|
* 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T56_SortList {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] head = {4, 2, 1, 3};
|
||||||
|
ListNode root = ListNodeUtils.build(head);
|
||||||
|
ListNode listNode = sortList(root);
|
||||||
|
ListNodeUtils.print(listNode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:尝试归并排序
|
||||||
|
* 速度击败21.59% 内存击败11.8% 16ms
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode sortList(ListNode head) {
|
||||||
|
if(head==null){
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListNode temp = head;
|
||||||
|
int length = 0;
|
||||||
|
while (temp != null) {
|
||||||
|
length++;
|
||||||
|
temp = temp.next;
|
||||||
|
}
|
||||||
|
return divide(head, 0, length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode divide(ListNode root, int start, int end) {
|
||||||
|
if (start >= end) {
|
||||||
|
return new ListNode(root.val);
|
||||||
|
}
|
||||||
|
int mid = start + ((end - start) >> 1);
|
||||||
|
int temp = mid - start;
|
||||||
|
ListNode midNode = root;
|
||||||
|
//root移动mid位
|
||||||
|
while (temp-- >= 0) {
|
||||||
|
midNode = midNode.next;
|
||||||
|
}
|
||||||
|
ListNode node1 = divide(root, start, mid);
|
||||||
|
ListNode node2 = divide(midNode, mid + 1, end);
|
||||||
|
return MergeSort(node1, node2);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode MergeSort(ListNode root1, ListNode root2) {
|
||||||
|
|
||||||
|
//两个都不是null,开始归并
|
||||||
|
ListNode head = new ListNode();
|
||||||
|
ListNode temp = head;
|
||||||
|
|
||||||
|
while (root1 != null && root2 != null) {
|
||||||
|
if (root1.val < root2.val) {
|
||||||
|
temp.next = new ListNode(root1.val);
|
||||||
|
root1 = root1.next;
|
||||||
|
} else {
|
||||||
|
temp.next = new ListNode(root2.val);
|
||||||
|
root2 = root2.next;
|
||||||
|
}
|
||||||
|
temp = temp.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root1 == null) {
|
||||||
|
temp.next = root2;
|
||||||
|
} else {
|
||||||
|
temp.next = root1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return head.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方归并排序:使用快慢指针遍历到中间位置
|
||||||
|
* 速度击败46.29% 内存击败88.11% 12ms
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode sortList1(ListNode head) {
|
||||||
|
return sortList(head, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode sortList(ListNode head, ListNode tail) {
|
||||||
|
if (head == null) {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
if (head.next == tail) {
|
||||||
|
head.next = null;
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
ListNode slow = head, fast = head;
|
||||||
|
while (fast != tail) {
|
||||||
|
slow = slow.next;
|
||||||
|
fast = fast.next;
|
||||||
|
if (fast != tail) {
|
||||||
|
fast = fast.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ListNode mid = slow;
|
||||||
|
ListNode list1 = sortList(head, mid);
|
||||||
|
ListNode list2 = sortList(mid, tail);
|
||||||
|
ListNode sorted = merge(list1, list2);
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListNode merge(ListNode head1, ListNode head2) {
|
||||||
|
ListNode dummyHead = new ListNode(0);
|
||||||
|
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
|
||||||
|
while (temp1 != null && temp2 != null) {
|
||||||
|
if (temp1.val <= temp2.val) {
|
||||||
|
temp.next = temp1;
|
||||||
|
temp1 = temp1.next;
|
||||||
|
} else {
|
||||||
|
temp.next = temp2;
|
||||||
|
temp2 = temp2.next;
|
||||||
|
}
|
||||||
|
temp = temp.next;
|
||||||
|
}
|
||||||
|
if (temp1 != null) {
|
||||||
|
temp.next = temp1;
|
||||||
|
} else if (temp2 != null) {
|
||||||
|
temp.next = temp2;
|
||||||
|
}
|
||||||
|
return dummyHead.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:时间复杂度O(N),直接记录各个值的数量,直接根据数量进行构造
|
||||||
|
* 速度击败99.74% 内存击败11.48% 3ms
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode sortList2(ListNode head) {
|
||||||
|
if (head == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int max = head.val;
|
||||||
|
int min = head.val;
|
||||||
|
ListNode node = head.next;
|
||||||
|
|
||||||
|
//记录链表中的最大值最小值
|
||||||
|
while (node != null) {
|
||||||
|
int val = node.val;
|
||||||
|
if (val > max) {
|
||||||
|
max = val;
|
||||||
|
}
|
||||||
|
if (val < min) {
|
||||||
|
min = val;
|
||||||
|
}
|
||||||
|
node = node.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
//记录每个值的数量
|
||||||
|
int[] count = new int[max - min + 1];
|
||||||
|
|
||||||
|
node = head;
|
||||||
|
while (node != null) {
|
||||||
|
count[node.val - min]++;
|
||||||
|
node = node.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
//直接根据每个值的数量进行构造
|
||||||
|
node = head;
|
||||||
|
max -= min;
|
||||||
|
for (int i = 0; i <= max; i++) {
|
||||||
|
int val = min + i;
|
||||||
|
while (count[i]-- > 0) {
|
||||||
|
node.val = val;
|
||||||
|
node = node.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-14 10:59
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣152题 乘积最大子数组:
|
||||||
|
* 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
|
||||||
|
* 测试用例的答案是一个 32-位 整数。
|
||||||
|
* 子数组 是数组的连续子序列。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T57_MaxProduct {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {-2, 3, -4};
|
||||||
|
System.out.println(maxProduct(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划法:
|
||||||
|
* TODO DP五部曲:
|
||||||
|
* 1.dp定义: dp[i][0]表示不要当前数的最大值 ; dp[i][1]表示要当前数的最大值
|
||||||
|
* 2.dp状态转移方程:
|
||||||
|
* 1.dp[i][0] 不要当前的数
|
||||||
|
* dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1])
|
||||||
|
* 2.dp[i][1] 要当前的数
|
||||||
|
* dp[i][1]=nums[i]
|
||||||
|
* dp[i][2]=dp[i-1][1]*nums[i]
|
||||||
|
* 3.dp初始化:dp[i][0]=Integer.Min dp[i][1]=nums[i]
|
||||||
|
* 4.dp遍历顺序:
|
||||||
|
* 5.dp举例推导:以nums=[2,3,-2,4]为例
|
||||||
|
* [2 3 -2 4]
|
||||||
|
* dp0: m 2 6 6
|
||||||
|
* dp1: 2 6 -2 4
|
||||||
|
* 尚且存在问题: 当前位置的最优解未必是前一个位置的最优解得出来的
|
||||||
|
* 只能过150个用例 [2,-5,-2,-4,3]无法通过 输出20 真实结果24
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProduct(int[] nums) {
|
||||||
|
|
||||||
|
int[][] dp = new int[nums.length][3];
|
||||||
|
dp[0][0] = Integer.MIN_VALUE;
|
||||||
|
dp[0][1] = nums[0];
|
||||||
|
dp[0][2] = nums[0];
|
||||||
|
int result = 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]);
|
||||||
|
dp[i][2] = dp[i - 1][2] * nums[i];
|
||||||
|
if (result < dp[i][0]) result = dp[i][0];
|
||||||
|
if (result < dp[i][1]) result = dp[i][1];
|
||||||
|
if (result < dp[i][2]) result = dp[i][2];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方题解: 也是动态规划法
|
||||||
|
* 1.根据当前位置的数是整数还是负数来进行判断
|
||||||
|
* 如果当前位置为正数 则希望前一个数尽可能的大
|
||||||
|
* 如果当前位置为负数 则希望前一个数尽可能的小
|
||||||
|
* 所以记录前一个数最大值和最小值
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProduct1(int[] nums) {
|
||||||
|
|
||||||
|
int length = nums.length;
|
||||||
|
int[] maxF = new int[length];
|
||||||
|
int[] minF = new int[length];
|
||||||
|
System.arraycopy(nums, 0, maxF, 0, length);
|
||||||
|
System.arraycopy(nums, 0, minF, 0, length);
|
||||||
|
int result=maxF[0];
|
||||||
|
for (int i = 1; i < length; ++i) {
|
||||||
|
maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(nums[i], minF[i - 1] * nums[i]));
|
||||||
|
minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(nums[i], maxF[i - 1] * nums[i]));
|
||||||
|
if(result<maxF[i])result=maxF[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 滚动数组优化
|
||||||
|
* 速度击败66.26% 内存击败42.45% 2ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int maxProduct2(int[] nums) {
|
||||||
|
|
||||||
|
int length = nums.length;
|
||||||
|
int maxF = nums[0];
|
||||||
|
int minF = nums[0];
|
||||||
|
|
||||||
|
int result=nums[0];
|
||||||
|
int temp;
|
||||||
|
for (int i = 1; i < length; ++i) {
|
||||||
|
temp=maxF;
|
||||||
|
maxF = Math.max(maxF * nums[i], Math.max(nums[i], minF * nums[i]));
|
||||||
|
minF = Math.min(minF * nums[i], Math.min(nums[i], temp * nums[i]));
|
||||||
|
if(result<maxF)result=maxF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-14 11:40
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣155题 最小栈:
|
||||||
|
* 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
|
||||||
|
* 实现 MinStack 类:
|
||||||
|
* MinStack() 初始化堆栈对象。
|
||||||
|
* void push(int val) 将元素val推入堆栈。
|
||||||
|
* void pop() 删除堆栈顶部的元素。
|
||||||
|
* int top() 获取堆栈顶部的元素。
|
||||||
|
* int getMin() 获取堆栈中的最小元素。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T58_MinStack {
|
||||||
|
|
||||||
|
public PriorityQueue<Integer> queue;
|
||||||
|
|
||||||
|
public Stack<Integer> stack;
|
||||||
|
|
||||||
|
|
||||||
|
//核心:又要有栈结构,又要在常数时间检索最小 ->一个栈 一个优先队列?
|
||||||
|
//通过 速度击败29.61% 内存击败41.67% 5ms
|
||||||
|
public T58_MinStack() {
|
||||||
|
queue=new PriorityQueue<>();
|
||||||
|
stack=new Stack<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(int val) {
|
||||||
|
stack.push(val);
|
||||||
|
queue.offer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pop() {
|
||||||
|
Integer pop = stack.pop();
|
||||||
|
queue.remove(pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int top() {
|
||||||
|
return stack.peek();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMin() {
|
||||||
|
return queue.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方解法:使用minStack来记录最小值,这时只要不pop那个最小值,stack就pop出的永远是那个最小值
|
||||||
|
* 速度击败94.9% 内存击败23.54% 4ms
|
||||||
|
*/
|
||||||
|
class MinStack {
|
||||||
|
private Deque<Integer> stack;
|
||||||
|
private Deque<Integer> minStack;
|
||||||
|
|
||||||
|
public MinStack() {
|
||||||
|
stack = new ArrayDeque<>();
|
||||||
|
minStack = new ArrayDeque<>();
|
||||||
|
minStack.push(Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void push(int val) {
|
||||||
|
stack.push(val);
|
||||||
|
minStack.push(Math.min(minStack.peek(), val));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pop() {
|
||||||
|
stack.pop();
|
||||||
|
minStack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int top() {
|
||||||
|
return stack.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMin() {
|
||||||
|
return minStack.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import com.markilue.leecode.listnode.ListNode;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-14 11:55
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣160题 相交链表:
|
||||||
|
* 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
|
||||||
|
*
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T59_GetIntersectionNode {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路1:hashset记录法
|
||||||
|
* 速度击败21.37% 内存击败87.13%
|
||||||
|
* @param headA
|
||||||
|
* @param headB
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
|
||||||
|
|
||||||
|
HashSet<ListNode> set = new HashSet<>();
|
||||||
|
|
||||||
|
while (headA != null) {
|
||||||
|
set.add(headA);
|
||||||
|
headA = headA.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (headB != null) {
|
||||||
|
if (set.contains(headB)) {
|
||||||
|
return headB;
|
||||||
|
}
|
||||||
|
headB = headB.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 两个分别走
|
||||||
|
* 速度击败97.91% 内存击败56.97% 1ms
|
||||||
|
*/
|
||||||
|
public ListNode getIntersectionNode1(ListNode headA, ListNode headB) {
|
||||||
|
|
||||||
|
ListNode tempA = headA;
|
||||||
|
ListNode tempB = headB;
|
||||||
|
|
||||||
|
while (tempA!= tempB) {
|
||||||
|
//如果不相交也会出去,因为两个最后都会遍历完A和B,然后都为null出去
|
||||||
|
tempA = tempA == null ? headB : tempA.next;
|
||||||
|
tempB = tempB == null ? headA : tempB.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-15 10:04
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣169题 多数元素:
|
||||||
|
* 给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
|
||||||
|
* 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T60_MajorityElement {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {3, 2, 3};
|
||||||
|
System.out.println(majorityElement(nums));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:按个遍历记录次数
|
||||||
|
* 由于数字大小不一定,所以不太好用数组记录,使用map记录
|
||||||
|
* 速度击败31.94% 内存击败22.92% 10ms
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int majorityElement(int[] nums) {
|
||||||
|
|
||||||
|
HashMap<Integer, Integer> map = new HashMap<>();//<num,count>
|
||||||
|
int length = nums.length;
|
||||||
|
|
||||||
|
for (int num : nums) {
|
||||||
|
int count = map.getOrDefault(num, 0) + 1;
|
||||||
|
if (count > length / 2) return num;
|
||||||
|
map.put(num, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快
|
||||||
|
* 速度击败100% 内存击败61.22%
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int majorityElement1(int[] nums) {
|
||||||
|
return dfs(nums, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int dfs(int[] nums, int start) {
|
||||||
|
int cnt = 1;
|
||||||
|
for (int i = start + 1; i < nums.length; i++) {
|
||||||
|
if (nums[i] == nums[start]) cnt++;
|
||||||
|
else cnt--;
|
||||||
|
if (cnt == 0) return dfs(nums, i + 1);
|
||||||
|
}
|
||||||
|
return nums[start];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上述思路的正常版
|
||||||
|
* 莫斯投票法
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int majorityElement2(int[] nums) {
|
||||||
|
int count = 1;
|
||||||
|
int maj = nums[0];
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
if (maj == nums[i])
|
||||||
|
count++;
|
||||||
|
else {
|
||||||
|
count--;//大于n/2的最后一定不会被减完
|
||||||
|
if (count == 0) {
|
||||||
|
maj = nums[i + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-15 10:26
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣198 打家劫舍:
|
||||||
|
* 你是一个专业的小偷,计划偷窃沿街的房屋。
|
||||||
|
* 每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,
|
||||||
|
* 如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
|
||||||
|
* 给定一个代表每个房屋存放金额的非负整数数组,
|
||||||
|
* 计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T61_Rob {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划法;可以分为当前位置偷;或者当前位置不偷
|
||||||
|
* @param nums
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int rob(int[] nums) {
|
||||||
|
|
||||||
|
int[][] dp = new int[nums.length][2];
|
||||||
|
dp[0][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] = dp[i - 1][0] + nums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp[nums.length - 1][0], dp[nums.length - 1][1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//滚动数组
|
||||||
|
public int rob1(int[] nums) {
|
||||||
|
|
||||||
|
|
||||||
|
int dp0 = 0;//不偷
|
||||||
|
int dp1 = nums[0];//偷
|
||||||
|
int temp;
|
||||||
|
|
||||||
|
for (int i = 1; i < nums.length; i++) {
|
||||||
|
temp = dp0;
|
||||||
|
dp0 = Math.max(dp0, dp1);
|
||||||
|
dp1 = temp + nums[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.max(dp0, dp1);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-15 10:36
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣200 岛屿数量:
|
||||||
|
* 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
|
||||||
|
* 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
|
||||||
|
* 此外,你可以假设该网格的四条边均被水包围。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T62_NumIslands {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
char[][] grid = {
|
||||||
|
{'1', '1', '1'},
|
||||||
|
{'0', '1', '0'},
|
||||||
|
{'1', '1', '1'},
|
||||||
|
};
|
||||||
|
System.out.println(numIslands(grid));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
char[][] grid = {
|
||||||
|
{'1', '0', '1', '1', '1'},
|
||||||
|
{'1', '0', '1', '0', '1'},
|
||||||
|
{'1', '1', '1', '0', '1'},
|
||||||
|
};
|
||||||
|
System.out.println(numIslands(grid));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean[][] used;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:动态规划?好像不动态规划也行
|
||||||
|
* 好像不行 前面的状态可能因为右面的而改变
|
||||||
|
* 贪心?碰上相连的就把与他相连的所有都变成 0
|
||||||
|
* @param grid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int numIslands(char[][] grid) {
|
||||||
|
|
||||||
|
used = new boolean[grid.length][grid[0].length];
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < grid.length; i++) {
|
||||||
|
for (int j = 0; j < grid[0].length; j++) {
|
||||||
|
if (grid[i][j] == '1') {
|
||||||
|
clear(grid, i, j);
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//贪心清空
|
||||||
|
public void clear(char[][] grid, int i, int j) {
|
||||||
|
if (i == grid.length || j == grid[0].length || j < 0 || i < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grid[i][j] == '1' && !used[i][j]) {
|
||||||
|
used[i][j] = true;
|
||||||
|
grid[i][j] = '0';
|
||||||
|
clear(grid, i + 1, j);
|
||||||
|
clear(grid, i - 1, j);//注意还要清理上边
|
||||||
|
clear(grid, i, j - 1);//注意还要清理左边
|
||||||
|
clear(grid, i, j + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void dfs(char[][] grid, int r, int c) {
|
||||||
|
if(r < 0 || c < 0 || r >= grid.length || c >= grid[0].length || grid[r][c] == '0'){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
grid[r][c] = '0';
|
||||||
|
dfs(grid,r + 1,c);
|
||||||
|
dfs(grid,r - 1,c);
|
||||||
|
dfs(grid,r,c - 1);
|
||||||
|
dfs(grid,r,c + 1);
|
||||||
|
}
|
||||||
|
//官方最快方法:与本人一致,但是他通过先把当前置为’0‘,可以不用使用used数组
|
||||||
|
public int numIslands1(char[][] grid) {
|
||||||
|
if(grid == null || grid.length == 0){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for(int i = 0;i < grid.length;i++){
|
||||||
|
for(int j = 0;j < grid[0].length;j++){
|
||||||
|
if(grid[i][j] == '1'){
|
||||||
|
count++;
|
||||||
|
dfs(grid,i,j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
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.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-15 11:29
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣206题 反转链表:
|
||||||
|
* 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T63_ReverseList {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
ListNode root = ListNodeUtils.build(new int[]{1, 2, 3, 4, 5});
|
||||||
|
ListNodeUtils.print(reverseList(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:使用另一个链表记录
|
||||||
|
* 速度击败100% 内存击败33.3%
|
||||||
|
* @param head
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ListNode reverseList(ListNode head) {
|
||||||
|
|
||||||
|
ListNode fake = new ListNode();
|
||||||
|
|
||||||
|
ListNode temp = head;
|
||||||
|
ListNode tempNext;
|
||||||
|
|
||||||
|
while (temp != null) {
|
||||||
|
ListNode listNode = new ListNode(temp.val);
|
||||||
|
listNode.next = fake.next;
|
||||||
|
fake.next = listNode;
|
||||||
|
temp=temp.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fake.next;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-16 09:54
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣207题 课程表:
|
||||||
|
* 你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
|
||||||
|
* 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
|
||||||
|
* 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
|
||||||
|
* 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T64_CanFinish {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test(){
|
||||||
|
int numCourses = 3;
|
||||||
|
int[][] prerequisites = {{1,0},{0,2},{2,1}};
|
||||||
|
System.out.println(canFinish(numCourses,prerequisites));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 思路:使用一个Map<Integer,List> 数字,需要在他后面学的东西
|
||||||
|
* 有问题 当处于一个循环的时候,后面没有办法再重新监测前面的内容
|
||||||
|
* 过了42个用例
|
||||||
|
* @param numCourses
|
||||||
|
* @param prerequisites
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean canFinish(int numCourses, int[][] prerequisites) {
|
||||||
|
|
||||||
|
HashMap<Integer, HashSet<Integer>> map = new HashMap<Integer, HashSet<Integer>>();
|
||||||
|
// HashSet<Integer> set1 = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
for (int[] prerequisite : prerequisites) {
|
||||||
|
// set1.add(prerequisite[0]);
|
||||||
|
// set1.add(prerequisite[1]);
|
||||||
|
if (!map.containsKey(prerequisite[1])) {
|
||||||
|
HashSet<Integer> set = new HashSet<>();
|
||||||
|
set.add(prerequisite[0]);
|
||||||
|
map.put(prerequisite[1], set);
|
||||||
|
}else {
|
||||||
|
HashSet<Integer> set = map.get(prerequisite[1]);
|
||||||
|
set.add(prerequisite[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(map.containsKey(prerequisite[0])){
|
||||||
|
//看看必须在他后面完成的里面有没有prerequisite[1]
|
||||||
|
if(map.get(prerequisite[0]).contains(prerequisite[1])){//1必须在0之前完成
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// if(set1.size()<numCourses){//课程数不够
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方深度优先算法:
|
||||||
|
* 时间复杂度O(N+M)
|
||||||
|
* 速度击败92..85% 内存击败38.86% 3ms
|
||||||
|
*/
|
||||||
|
List<List<Integer>> edges;
|
||||||
|
int[] visited;
|
||||||
|
boolean valid = true;
|
||||||
|
|
||||||
|
public boolean canFinish1(int numCourses, int[][] prerequisites) {
|
||||||
|
edges = new ArrayList<List<Integer>>();
|
||||||
|
for (int i = 0; i < numCourses; ++i) {
|
||||||
|
edges.add(new ArrayList<Integer>());
|
||||||
|
}
|
||||||
|
visited = new int[numCourses];
|
||||||
|
for (int[] info : prerequisites) {
|
||||||
|
edges.get(info[1]).add(info[0]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < numCourses && valid; ++i) {
|
||||||
|
if (visited[i] == 0) {
|
||||||
|
dfs(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dfs(int u) {
|
||||||
|
visited[u] = 1;//搜索中
|
||||||
|
for (int v: edges.get(u)) {
|
||||||
|
if (visited[v] == 0) {
|
||||||
|
dfs(v);
|
||||||
|
if (!valid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (visited[v] == 1) {//肯定要在当前节点之后进行遍历到才行
|
||||||
|
valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visited[u] = 2;//已完成
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:
|
||||||
|
* 速度击败100% 内存击败93.16% 1ms
|
||||||
|
* @param numCourses
|
||||||
|
* @param prerequisites
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean canFinish2(int numCourses, int[][] prerequisites) {
|
||||||
|
int len = prerequisites.length;
|
||||||
|
if (len == 0) return true;
|
||||||
|
int[] pointer = new int[numCourses];// 每个课程被指向的次数
|
||||||
|
for (int[] p : prerequisites) ++pointer[p[1]];
|
||||||
|
boolean[] removed = new boolean[len];// 标记prerequisites中的元素是否被移除
|
||||||
|
int remove = 0;// 移除的元素数量
|
||||||
|
while (remove < len) {
|
||||||
|
int currRemove = 0;// 本轮移除的元素数量
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (removed[i]) continue;// 被移除的元素跳过
|
||||||
|
int[] p = prerequisites[i];
|
||||||
|
if (pointer[p[0]] == 0) {// 如果被安全课程指向
|
||||||
|
--pointer[p[1]];// 被指向次数减1
|
||||||
|
removed[i] = true;
|
||||||
|
++currRemove;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currRemove == 0) return false;// 如果一轮跑下来一个元素都没移除,则没必要进行下一轮
|
||||||
|
remove += currRemove;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.markilue.leecode.hot100;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*@BelongsProject: Leecode
|
||||||
|
*@BelongsPackage: com.markilue.leecode.hot100
|
||||||
|
*@Author: markilue
|
||||||
|
*@CreateTime: 2023-03-16 11:09
|
||||||
|
*@Description:
|
||||||
|
* TODO 力扣215 数组中的第K个最大元素:
|
||||||
|
* 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
|
||||||
|
* 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
|
||||||
|
* 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
|
||||||
|
*@Version: 1.0
|
||||||
|
*/
|
||||||
|
public class T66_FindKthLargest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() {
|
||||||
|
int[] nums = {3, 2, 1, 5, 6, 4};
|
||||||
|
int k = 2;
|
||||||
|
System.out.println(findKthLargest(nums, k));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test1() {
|
||||||
|
int[] nums = {2, 1};
|
||||||
|
int k = 1;
|
||||||
|
System.out.println(findKthLargest(nums, k));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快排的思路:通过partition找到对应的位置,然后抛弃另一半
|
||||||
|
* 速度击败64.38% 内存击败78.98% 14ms
|
||||||
|
* @param nums
|
||||||
|
* @param k
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int findKthLargest(int[] nums, int k) {
|
||||||
|
return partition(nums, 0, nums.length - 1, nums.length - k);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int partition(int[] nums, int start, int end, int k) {
|
||||||
|
if (start == end) {
|
||||||
|
return nums[start];
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = sort(nums, start, end);
|
||||||
|
|
||||||
|
if (index < k) return partition(nums, index + 1, end, k);
|
||||||
|
else if (index > k) return partition(nums, start, index - 1, k);
|
||||||
|
else return nums[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int sort(int[] nums, int start, int end) {
|
||||||
|
|
||||||
|
int left = start;
|
||||||
|
int right = end + 1;
|
||||||
|
int compare = nums[start];
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
|
||||||
|
while (++left < nums.length && nums[left] < compare) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (--right >= 0 && nums[right] > compare) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left > right) break;
|
||||||
|
swap(nums, left, right);
|
||||||
|
}
|
||||||
|
swap(nums, start, right);
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void swap(int[] nums, int left, int right) {
|
||||||
|
int temp = nums[left];
|
||||||
|
nums[left] = nums[right];
|
||||||
|
nums[right] = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 官方最快:记录每个数出现的次数,然后进行相减,当k<=0就是所需要的那个数
|
||||||
|
* 速度击败100% 内存击败5% 1ms
|
||||||
|
* @param nums
|
||||||
|
* @param k
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int findKthLargest1(int[] nums, int k) {
|
||||||
|
|
||||||
|
int max = nums[0];
|
||||||
|
int min = nums[0];
|
||||||
|
//寻找最大值和最小值
|
||||||
|
for (int num : nums) {
|
||||||
|
if (max < num) {
|
||||||
|
max = num;
|
||||||
|
}
|
||||||
|
if (min > num) {
|
||||||
|
min = num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] allnums = new int[max - min + 1];
|
||||||
|
|
||||||
|
for (int num : nums) {
|
||||||
|
allnums[num - min]++;//记录每个数出现的次数
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = max - min; i >= 0; i--) {
|
||||||
|
k = k - allnums[i];//次数相减
|
||||||
|
if (k <= 0) {
|
||||||
|
return i + min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue