From 1e6629760fb0ad7e9a72b53b43417080fa757e24 Mon Sep 17 00:00:00 2001 From: markilue <745518019@qq.com> Date: Wed, 8 Mar 2023 21:47:45 +0800 Subject: [PATCH] =?UTF-8?q?leecode=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../leecode/hot100/T31_MinPathSum.java | 93 +++++++ .../leecode/hot100/T32_ClimbStairs.java | 57 ++++ .../leecode/hot100/T33_MinDistance.java | 99 +++++++ .../leecode/hot100/T34_SortColors.java | 102 +++++++ .../leecode/hot100/T35_MinWindow.java | 251 ++++++++++++++++++ 5 files changed, 602 insertions(+) create mode 100644 Leecode/src/main/java/com/markilue/leecode/hot100/T31_MinPathSum.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/hot100/T32_ClimbStairs.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/hot100/T33_MinDistance.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/hot100/T34_SortColors.java create mode 100644 Leecode/src/main/java/com/markilue/leecode/hot100/T35_MinWindow.java diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T31_MinPathSum.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T31_MinPathSum.java new file mode 100644 index 0000000..25fc3d8 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T31_MinPathSum.java @@ -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]; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T32_ClimbStairs.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T32_ClimbStairs.java new file mode 100644 index 0000000..d52b5d4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T32_ClimbStairs.java @@ -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]; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T33_MinDistance.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T33_MinDistance.java new file mode 100644 index 0000000..e3cdb67 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T33_MinDistance.java @@ -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]; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T34_SortColors.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T34_SortColors.java new file mode 100644 index 0000000..ec648cc --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T34_SortColors.java @@ -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; + } + } + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T35_MinWindow.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T35_MinWindow.java new file mode 100644 index 0000000..cb792aa --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T35_MinWindow.java @@ -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 map = new HashMap<>();// + + //记录每个出现的次数 + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + map.put(c, map.getOrDefault(c, 0) + 1); + } + + Map, Integer> list = new HashMap<>();//记录各个字母出现的位置 + //寻找每个字母在s中出现的次数 + for (Map.Entry entry : map.entrySet()) { + Character key = entry.getKey(); + Integer count = entry.getValue(); + int start = -1; + ArrayList 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, Integer> listIntegerEntry : list.entrySet()) { + List 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 window = new HashMap<>(); + Map 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 need = new HashMap<>();//记录t的需求 + HashMap 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); + } + +}