diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T69_IsPalindrome.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T69_IsPalindrome.java new file mode 100644 index 0000000..95e8ace --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T69_IsPalindrome.java @@ -0,0 +1,136 @@ +package com.markilue.leecode.hot100; + +import com.markilue.leecode.listnode.ListNode; + +import java.util.List; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hot100 + *@Author: markilue + *@CreateTime: 2023-03-17 09:52 + *@Description: + * TODO 力扣234 回文链表: + * 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。 + *@Version: 1.0 + */ +public class T69_IsPalindrome { + + + /** + * 思路:遍历一边,用数组记录下来,在进行判断? + * 速度击败17.97% 内存击败94.89% 16ms + * @param head + * @return + */ + public boolean isPalindrome(ListNode head) { + + if (head == null) return true; + + StringBuilder sb = new StringBuilder(); + ListNode temp = head; + while (temp != null) { + sb.append(temp.val); + temp = temp.next; + } + return eval(sb); + } + + public boolean eval(StringBuilder sb) { + + int left = 0; + int right = sb.length() - 1; + + while (left < right) { + if (sb.charAt(left) != sb.charAt(right)) return false; + left++; + right--; + } + + return true; + } + + + boolean flag = true; + + /** + * 将head传递下去?递归判断法 + * 速度击败25.8% 内存击败15.82% 11ms + * + * @param head + * @return + */ + public boolean isPalindrome1(ListNode head) { + RecurEval(head, head); + return flag; + } + + + public ListNode RecurEval(ListNode cur, ListNode head) { + if (cur.next == null) { + if (cur.val == head.val) return head.next; + else { + flag = false; + return null; + } + } + + ListNode listNode = RecurEval(cur.next, head); + if (listNode == null) return null; + if (cur.val == listNode.val) return listNode.next; + else { + flag = false; + return null; + } + } + + + + + + /** O(n) time and O(1) space + * mid + * 1 -> 2 -> 3 -> 2 -> 1 + * 1 -> 2 -> 3 -> 1 -> 2 reverse(mid.next) + * p1 p2 + * 官方最快: 快慢指针+反转链表 + * 速度击败63.33% 内存击败38.21% 5ms 更快的都是相同的方法 + */ + public boolean isPalindrome2(ListNode head) { + if (head == null) { + return true; + } + ListNode mid = findMid(head); + ListNode p2 = reverse(mid.next); + ListNode p1 = head; + while (p2 != null) {//p2!=null是重点,p2短一些 + if (p1.val != p2.val) { + return false; + } + p1 = p1.next; + p2 = p2.next; + } + return true; + } + + private ListNode findMid(ListNode head) { + ListNode fast = head, slow = head; + //必须加fast.next != null,不然null pointer + while (fast.next != null && fast.next.next != null) { + fast = fast.next.next; + slow = slow.next; + } + return slow; + } + + private ListNode reverse(ListNode head) { + ListNode prev = null; + while(head != null) { + ListNode temp = head.next; + head.next = prev; + prev = head; + head = temp; + } + return prev; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T70_LowestCommonAncestor.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T70_LowestCommonAncestor.java new file mode 100644 index 0000000..ccaa5ee --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T70_LowestCommonAncestor.java @@ -0,0 +1,53 @@ +package com.markilue.leecode.hot100; + +import com.markilue.leecode.tree.TreeNode; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hot100 + *@Author: markilue + *@CreateTime: 2023-03-17 10:19 + *@Description: + * TODO 力扣236 二叉树的最近公共祖先: + * 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 + * 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x, + * 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” + *@Version: 1.0 + */ +public class T70_LowestCommonAncestor { + + + /** + * 速度击败31.65% 内存击败71.14% 7ms + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + if (root == null) return null; + if (root == p || root == q) return root;//提前碰到某个节点了 + TreeNode left = lowestCommonAncestor(root.left, p, q); + + TreeNode right = lowestCommonAncestor(root.right, p, q); + if (left == null) return right; + if (right == null) return left; + return root;//两个都不相等就返回自己,证明一个在左边一个在右边 + } + + + /** + * 比较快的那种写法 + * @param root + * @param p + * @param q + * @return + */ + public TreeNode lowestCommonAncestor1(TreeNode root, TreeNode p, TreeNode q) { + if (root == null || root == p || root == q) return root; + TreeNode left = lowestCommonAncestor1(root.left, p, q); + TreeNode right = lowestCommonAncestor1(root.right, p, q); + if (left != null && right != null) {return root;} + return left != null ? left : right; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T71_ProductExceptSelf.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T71_ProductExceptSelf.java new file mode 100644 index 0000000..d26b8b8 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T71_ProductExceptSelf.java @@ -0,0 +1,84 @@ +package com.markilue.leecode.hot100; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hot100 + *@Author: markilue + *@CreateTime: 2023-03-17 10:41 + *@Description: + * TODO 力扣238 除自身以外数组的乘积: + * 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 + * 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 + * 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 + *@Version: 1.0 + */ +public class T71_ProductExceptSelf { + + + /** + * 速度击败31.9% 内存击败6.68% 2ms + * @param nums + * @return + */ + public int[] productExceptSelf(int[] nums) { + int length = nums.length; + + // L 和 R 分别表示左右两侧的乘积列表 + int[] L = new int[length]; + int[] R = new int[length]; + + int[] answer = new int[length]; + + // L[i] 为索引 i 左侧所有元素的乘积 + // 对于索引为 '0' 的元素,因为左侧没有元素,所以 L[0] = 1 + L[0] = 1; + for (int i = 1; i < length; i++) { + L[i] = nums[i - 1] * L[i - 1]; + } + + // R[i] 为索引 i 右侧所有元素的乘积 + // 对于索引为 'length-1' 的元素,因为右侧没有元素,所以 R[length-1] = 1 + R[length - 1] = 1; + for (int i = length - 2; i >= 0; i--) { + R[i] = nums[i + 1] * R[i + 1]; + } + + // 对于索引 i,除 nums[i] 之外其余各元素的乘积就是左侧所有元素的乘积乘以右侧所有元素的乘积 + for (int i = 0; i < length; i++) { + answer[i] = L[i] * R[i]; //重点是这:左乘上右 ->left只考虑了左边,right只考虑了右边,两者相乘就是除自身以外的乘积 + } + + return answer; + } + + + /** + * 官方空间复杂度O(1)优化,动态构造 + * 速度击败100% 内存击败75.15% 1ms + * @param nums + * @return + */ + public int[] productExceptSelf1(int[] nums) { + int length = nums.length; + int[] answer = new int[length]; + + // answer[i] 表示索引 i 左侧所有元素的乘积 + // 因为索引为 '0' 的元素左侧没有元素, 所以 answer[0] = 1 + answer[0] = 1; + for (int i = 1; i < length; i++) { + answer[i] = nums[i - 1] * answer[i - 1]; + } + + // R 为右侧所有元素的乘积 + // 刚开始右边没有元素,所以 R = 1 + int R = 1; + for (int i = length - 1; i >= 0; i--) { + // 对于索引 i,左边的乘积为 answer[i],右边的乘积为 R + answer[i] = answer[i] * R; + // R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上 + R *= nums[i]; + } + return answer; + } + +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T72_MaxSlidingWindow.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T72_MaxSlidingWindow.java new file mode 100644 index 0000000..84441fa --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T72_MaxSlidingWindow.java @@ -0,0 +1,111 @@ +package com.markilue.leecode.hot100; + +import org.junit.Test; + +import java.util.ArrayDeque; +import java.util.Comparator; +import java.util.PriorityQueue; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hot100 + *@Author: markilue + *@CreateTime: 2023-03-17 11:08 + *@Description: + * TODO 力扣239 滑动窗口最大值: + * 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 + * 返回 滑动窗口中的最大值 。 + *@Version: 1.0 + */ +public class T72_MaxSlidingWindow { + + @Test + public void test() { + int[] nums = {1, 3, -1, -3, 5, 3, 6, 7}; + int k = 3; + System.out.println(maxSlidingWindow(nums, k)); + } + + @Test + public void test1() { + int[] nums = {1}; + int k = 1; + System.out.println(maxSlidingWindow(nums, k)); + } + + + /** + * 后续的一个最大值: + * 使用一个优先队列来维护最大值,过期元素去除 + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow(int[] nums, int k) { + + PriorityQueue queue = new PriorityQueue<>(new Comparator() { + @Override + public int compare(int[] o1, int[] o2) { + return o2[0] - o1[0]; + } + }); + + for (int i = 0; i < k && i < nums.length; i++) { + queue.add(new int[]{nums[i], i}); + } + + if (nums.length < k) return new int[]{queue.peek()[0]}; + + + int[] result = new int[nums.length - k + 1]; + result[0] = queue.peek()[0]; + + for (int i = k; i < nums.length; i++) { + + queue.add(new int[]{nums[i], i}); + + //移除过期元素 + while (!queue.isEmpty() && i - queue.peek()[1] >= k) { + queue.poll(); + } + result[i - k + 1] = queue.peek()[0]; + } + + return result; + + } + + + /** + * 官方较快:维护一个单调队列即可 + * 速度击败71.45% 内存击败46.7% 29ms + * @param nums + * @param k + * @return + */ + public int[] maxSlidingWindow1(int[] nums, int k) { + ArrayDeque deque = new ArrayDeque<>(); + int n = nums.length; + int[] res = new int[n - k + 1]; + int idx = 0; + for(int i = 0; i < n; i++) { + // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点 + // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出 + while(!deque.isEmpty() && deque.peek() < i - k + 1){ + deque.poll(); + } + // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出 + while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {//依次跟末尾比 + deque.pollLast(); + } + + deque.offer(i); + + // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了 + if(i >= k - 1){ + res[idx++] = nums[deque.peek()]; + } + } + return res; + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T73_SearchMatrix.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T73_SearchMatrix.java new file mode 100644 index 0000000..1503e3d --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T73_SearchMatrix.java @@ -0,0 +1,139 @@ +package com.markilue.leecode.hot100; + +import org.junit.Test; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.hot100 + *@Author: markilue + *@CreateTime: 2023-03-17 11:32 + *@Description: + * TODO 力扣240 搜索二维矩阵II: + * 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性: + * 每行的元素从左到右升序排列。 + * 每列的元素从上到下升序排列。 + *@Version: 1.0 + */ +public class T73_SearchMatrix { + + @Test + public void test() { + int[][] matrix = { + {1, 4, 7, 11, 15}, + {2, 5, 8, 12, 19}, + {3, 6, 9, 16, 22}, + {10, 13, 14, 17, 24}, + {18, 21, 23, 26, 30}}; + int target = 20; + + System.out.println(searchMatrix(matrix, target)); + } + + @Test + public void test1() { + int[][] matrix = { + {-1, 3}}; + int target = 3; + + System.out.println(searchMatrix(matrix, target)); + } + + + boolean[][] visited; + + /** + * 思路:直接依次判断对角行不行,来设计一个横行和一个竖行 + * 速度击败5.38% 内存击败98.29% 77ms + * @param matrix + * @param target + * @return + */ + public boolean searchMatrix(int[][] matrix, int target) { + visited = new boolean[matrix.length][matrix[0].length]; + return search(matrix, 0, 0, target); + } + + public boolean search(int[][] matrix, int i, int j, int target) { + + if (i < 0 || j < 0 || i >= matrix.length || j >= matrix[0].length) { + return false; + } + + + if (!visited[i][j]) { + visited[i][j] = true; + if (matrix[i][j] < target) { + return search(matrix, i + 1, j + 1, target) || search(matrix, i + 1, j, target) || search(matrix, i, j + 1, target); + } else if (matrix[i][j] > target) { + return search(matrix, i - 1, j, target) || search(matrix, i, j - 1, target); + } else { + return true; + } + } + return false; + } + + + /** + * 官方最快:Z 字形查找 + * 时间复杂度O(m+n) + * TODO 合理性理由: + * 1)如果 matrix[x,y]=target,说明搜索完成; + * 2)如果 matrix[x,y]>target,由于每一列的元素都是升序排列的,那么在当前的搜索矩阵中,所有位于第 y 列的元素都是严格大于target 的,因此我们可以将它们全部忽略,即将 yyy 减少 111; + * 3)如果 matrix[x,y]= 0) { + if (matrix[i][j] == target) { + return true; + } + + if (matrix[i][j] > target) { + j--; + } else { + i++; + } + } + + return false; + } + + + /** + * 官方第二快:二分法 + * 速度击败38.68% 内存击败52.77% 6ms + * @param matrix + * @param target + * @return + */ + public boolean searchMatrix2(int[][] matrix, int target) { + for (int[] ints : matrix) { + if(search(ints,target,0,ints.length-1)!=-1)return true; + } + + return false; + } + + public int search(int[] matrix, int target, int start, int end) { + if (start == end) { + if (matrix[start] == target) return start; + else return -1; + } + + int mid = start + ((end - start) >> 1); + if (matrix[mid] > target) { + return search(matrix, target, start, mid - 1); + } else if (matrix[mid] < target) { + return search(matrix, target, mid + 1, end); + } else { + return mid; + } + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/hot100/T74_NumSquares.java b/Leecode/src/main/java/com/markilue/leecode/hot100/T74_NumSquares.java new file mode 100644 index 0000000..22490f8 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/hot100/T74_NumSquares.java @@ -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-17 12:16 + *@Description: + * TODO 力扣279题 完全平方数: + * 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 + * 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。 + *@Version: 1.0 + */ +public class T74_NumSquares { + + @Test + public void test(){ + int n=12; + System.out.println(numSquares(n)); + } + + + /** + * 思路:一次判断他用什么组合最少 + * 速度击败43.45% 内存击败49.78% 41ms + * @param n + * @return + */ + public int numSquares(int n) { + + int[] dp = new int[n + 1]; + dp[1] = 1; + + for (int i = 2; i < dp.length; i++) { + dp[i] = i;//最多为他自身 + for (int j = 2; j * j <= i; j++) {//平方数 + dp[i] = Math.min(dp[i], dp[i - j * j] + 1); + } + } + + return dp[n]; + + } +} diff --git a/Leecode/src/main/java/com/markilue/leecode/test/testAnt.java b/Leecode/src/main/java/com/markilue/leecode/test/testAnt.java new file mode 100644 index 0000000..80406a4 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/test/testAnt.java @@ -0,0 +1,109 @@ +package com.markilue.leecode.test; + +import java.util.HashMap; +import java.util.Scanner; + +/** + *@BelongsProject: Leecode + *@BelongsPackage: com.markilue.leecode.test + *@Author: markilue + *@CreateTime: 2023-03-16 19:33 + *@Description: TODO + *@Version: 1.0 + */ +public class testAnt { + + + public static void main(String[] args) { + + Scanner sc = new Scanner(System.in); + String input = sc.nextLine(); + + new testAnt().test(input); +// StringBuilder output = new StringBuilder(); +// int left = 0; +// +// //去除前面的 +// while (left < input.length() && input.charAt(left) == ' ') { +// left++; +// } +// +// boolean isCapital = true; // 首字母是否大写 +// for (int i = left; i < input.length(); i++) { +// char c = input.charAt(i); +// if (c == ' ') { +// if (i > 0 && input.charAt(i - 1) == '.') { +// output.deleteCharAt(output.length() - 1); +// } +// // 跳过多余的空格 +// while (i < input.length() - 1 && input.charAt(i + 1) == ' ') { +// i++; +// } +// if (input.charAt(i + 1) != '.') { +// output.append(' '); +// } +// } else if (c == '.') { +// // 句号后面要加一个空格 +// output.append(". "); +// isCapital = true; // 下一句话的首字母要大写 +// } else { +// if (isCapital) { +// // 首字母要大写 +// output.append(Character.toUpperCase(c)); +// isCapital = false; +// } else { +// output.append(c); +// } +// } +// } +// +// System.out.println(output.toString()); + sc.close(); + + } + + + public void test(String str) { + + + int left = 0; + + //去除前面的 + while (left < str.length() && str.charAt(left) == ' ') { + left++; + } + + StringBuilder sb = new StringBuilder(); + sb.append(Character.toUpperCase(str.charAt(left++))); + boolean flag = false; + + while (left < str.length()) { + char c = str.charAt(left); + if (c == '.') { + int temp = sb.length(); + while (sb.charAt(--temp)==' ')sb.deleteCharAt(sb.length()-1); + sb.append(". "); + flag = true; + } else if (c == ' ') { + char c1 = str.charAt(left - 1); + if (c1 != ' ' && c1 != '.') { + sb.append(c); + } + } else { + if (flag) { + sb.append(Character.toUpperCase(c)); + flag = false; + } else { + sb.append(c); + } + } + left++; + + } + + + System.out.println(sb.toString()); + + + } +}