刷题顺序转自:ACM金牌选手整理的【LeetCode刷题顺序】_清澈不在远方-CSDN博客
题解为本人自刷
数据结构
数组&双指针
-
/** * 给定一个整数数组nums和一个整数目标值target,请你在该数组中找出 和为目标值target的那两个整数,并返回它们的数组下标。 * 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 * 你可以按任意顺序返回答案。 * * 解法:用哈希表来存,时间复杂度:O(N),空间复杂度:O(N) */ class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> valueToIndexMap = new HashMap<>(); for (int i = 0; i < nums.length; i++) { if (valueToIndexMap.containsKey(target - nums[i])) { return new int[] {i, valueToIndexMap.get(target - nums[i])}; } else { valueToIndexMap.put(nums[i], i); } } return new int[0]; }
-
//给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。 class Solution1 { 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++) { 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; } }
class Solution2 { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int length1 = nums1.length, length2 = nums2.length; int totalLength = length1 + length2; if (totalLength % 2 == 1) { int midIndex = totalLength / 2; return getKthElement(nums1, nums2, midIndex + 1); } else { int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2; return (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0; } } private double getKthElement(int[] nums1, int[] nums2, int k) { int length1 = nums1.length, length2 = nums2.length; int index1 = 0, index2 = 0; while (true) { // 边界情况 if (index1 == length1) { return nums2[index2 + k - 1]; } if (index2 == length2) { return nums1[index1 + k - 1]; } if (k == 1) { return Math.min(nums1[index1], nums2[index2]); } // 正常情况 int half = k / 2; int newIndex1 = Math.min(index1 + half, length1) - 1; int newIndex2 = Math.min(index2 + half, length2) - 1; int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { // 如果 nums1[k/2−1]<nums2[k/2−1],则比 nums1[k/2−1] 小的数最多只有nums1的前k/2−1个数和nums2的前k/2-1个数, // 即比nums1[k/2−1]小的数最多只有k-2个,因此 nums1[k/2−1]不可能是第k个数, // nums1[0]到nums1[k/2−1]也都不可能是第k个数,可以全部排除。 // k要减去被排除的数,当前位置的也排除所以+1 k -= (newIndex1 - index1 + 1); // 被排除的数组初始下标增加 index1 = newIndex1 + 1; } else { k -= (newIndex2 - index2 + 1); index2 = newIndex2 + 1; } } } }
-
// 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 // 解法:排序+指针 second和third指针分别增加和减小 class Solution { public List<List<Integer>> threeSum(int[] nums) { int length = nums.length; Arrays.sort(nums); List<List<Integer>> result = new ArrayList<>(); for (int first = 0; first < length - 2; first++) { int third = length - 1; if (first == 0 || nums[first] != nums[first - 1]) { for (int second = first + 1; second < length - 1; second++) { // 遇到重复的second略过 if (second > first + 1 && nums[second] == nums[second - 1]) { continue; } while (nums[first] + nums[second] + nums[third] > 0 && second < third) { third--; } if (second == third) { break; } if (nums[first] + nums[second] + nums[third] == 0) { List<Integer> list = new ArrayList<>(); list.add(nums[first]); list.add(nums[second]); list.add(nums[third]); result.add(list); } } } } return result; } }
-
//给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 class Solution { public void sortColors(int[] nums) { // 代表下一个放0和2的位置,初始为数组两端 int p0 = 0; int p2 = nums.length - 1; for (int i = 0; i <= p2; i++) { // 保证换回来的不是2 while (nums[i] == 2 && i <= p2) { int tmp = nums[p2]; nums[p2] = nums[i]; nums[i] = tmp; p2--; } if (nums[i] == 0) { int tmp = nums[p0]; nums[p0] = nums[i]; nums[i] = tmp; p0++; } } } }
-
// 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 class Solution { Map<Character, Integer> target = new HashMap<>(); // 目标字符串计数 Map<Character, Integer> window = new HashMap<>(); // 记录滑动窗口中的character及频率 int ansL = -1;// 结果的左指针 int ansR = -1;// 结果的右指针 int len = Integer.MAX_VALUE; // 记录每次匹配符合时子串的长度 public String minWindow(String s, String t) { for (int i = 0; i < t.length(); i++) { char c = t.charAt(i); target.put(c, target.getOrDefault(c, 0) + 1); } // 左、右指针 int l = 0; int r = -1; while (r < s.length()) { r++; // 可能window中一个字符出现的次数要多于我们要比对的那个字符串中字符的个数 if (r < s.length() && target.containsKey(s.charAt(r))) { // 如果r存在于目标字符串 window.put(s.charAt(r), window.getOrDefault(s.charAt(r), 0) + 1); } while (check() && l <= r) { //这里检查左指针是否可以收缩窗口,注意左指针可以收缩窗口的条件在于:窗口中包含了字符t // 能够走到这里就说明已经符合要求了,但是要找到全局最小的子串,所以每次都要判断一下 if (r - l + 1 < len) { ansL = l; ansR = r; len = r - l + 1; } // 如果window中的左边出现了t中的字符,直接减1,然后看是否还能满足完全覆盖的要求 if (target.containsKey(s.charAt(l))) { window.put(s.charAt(l), window.getOrDefault(s.charAt(l), 0) - 1); } l++; } } return ansR == -1 ? "" : s.substring(ansL, ansR + 1); //ansR==-1 说明没有符合的,就返回空字符串 } // 用于检测是否窗口中是否完全覆盖了子串 public boolean check() { for (Map.Entry<Character, Integer> e : target.entrySet()) { int val = e.getValue(); if (window.getOrDefault(e.getKey(), 0) < val) { return false; } } return true; } }
-
//给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 public class Solution2 { public ListNode detectCycle(ListNode head) { // 快慢指针 fast两倍速,假设两指针能相遇,假设快指针已经走了n圈,慢指针仍然1圈都没走完(因为是2倍速,所以必能在一圈未走完时追上) // 假设直线距离段为a,相遇的那半圈为b,剩下半圈为c。快指针走过的距离为a+(n+1)b+nc,慢指针走过的距离为a+b,则a+(n+1)b+nc=2(a+b) // a=c+(n−1)(b+c),慢指针距离入环处距离为c,则再起一个指针从起点出发1倍速,必能在入环处与慢指针相遇 if (head == null) { return null; } ListNode slow = head; ListNode fast = head; while (fast.next != null) { slow = slow.next; if (fast.next != null) { fast = fast.next.next; } else { return null; } if (fast == slow) { ListNode listNode = head; while (listNode != slow) { listNode = listNode.next; slow = slow.next; } return listNode; } } return null; } }
class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } } //给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 public class Solution { public ListNode detectCycle(ListNode head) { // 哈希表存储,遇到相同节点则存在环 ListNode pos = head; Set<ListNode> visited = new HashSet<>(); while (pos != null) { if (visited.contains(pos)) { return pos; } else { visited.add(pos); } pos = pos.next; } return null; } }
-
//请判断一个链表是否为回文链表。 class Solution { public boolean isPalindrome(ListNode head) { if (head == null) { return true; } // 找到前半部分链表的尾节点 ListNode firstHalfEnd = endOfFirstHalf(head); // 反转后半部分链表 ListNode secondHalfStart = reverseList(firstHalfEnd.next); // 判断是否回文 ListNode p1 = head; ListNode p2 = secondHalfStart; boolean result = true; while (result && p2 != null) { if (p1.val != p2.val) { result = false; } p1 = p1.next; p2 = p2.next; } // 还原链表并返回结果 firstHalfEnd.next = reverseList(secondHalfStart); return result; } /** * 反转链表 * * @param head head * @return 反转后的头结点 */ private ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode nextTemp = curr.next; curr.next = prev; prev = curr; curr = nextTemp; } return prev; } /** * 快慢指针找到中间节点,若链表有奇数个节点,则中间的节点应该看作是前半部分。 * @param head head * @return 后半部分的头结点 */ private ListNode endOfFirstHalf(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } } //leetcode submit region end(Prohibit modification and deletion) public class ListNode { int val; ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
-
class Solution { // 使用双指针,左指针指向当前已经处理好的序列的下一个,右指针指向待处理序列的头部。 // 右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。 public void moveZeroes(int[] nums) { int left = 0; int right = 0; while (right < nums.length) { if (nums[right] != 0) { swap(nums, left, right); left++; } right++; } } private void swap(int[] nums, int left, int right) { int tmp = nums[right]; nums[right] = nums[left]; nums[left] = tmp; } }
链表
-
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // 增加个头结点,方便删除 ListNode dummy = new ListNode(0, head); ListNode first = head; ListNode second = dummy; for (int i = 0; i < n; i++) { first = first.next; } while (first != null) { first = first.next; second = second.next; } second.next = second.next.next; return dummy.next; } } public class ListNode { int val; ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
-
// 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。 class Solution { public ListNode sortList(ListNode head) { // 自底向上归并排序 时间复杂度:O(nlogn),其中 nn 是链表的长度。 空间复杂度:O(1)。 if (head == null) { return head; } int length = 0; ListNode node = head; while (node != null) { length++; node = node.next; } // 哨兵结点 ListNode dummyHead = new ListNode(0, head); for (int subLength = 1; subLength < length; subLength <<= 1) { ListNode prev = dummyHead, curr = dummyHead.next; while (curr != null) { // 归并第一段的头结点 ListNode head1 = curr; for (int i = 1; i < subLength && curr.next != null; i++) { curr = curr.next; } // 归并第二段的头结点 ListNode head2 = curr.next; // 截断待排序的第一段尾部 curr.next = null; curr = head2; for (int i = 1; i < subLength && curr != null && curr.next != null; i++) { curr = curr.next; } ListNode next = null; if (curr != null) { // 后面还有待排序的,截断第二段的尾部 next = curr.next; curr.next = null; } // 将两链表排序并合并 ListNode merged = merge(head1, head2); prev.next = merged; while (prev.next != null) { // pre移动到已排序部分的尾部 prev = prev.next; } curr = next; } } return dummyHead.next; } /** * 合并俩链表 * * @param head1 链表1的头结点 * @param head2 链表2的头结点 * @return 合并后的链表的头结点 */ public ListNode merge(ListNode head1, ListNode head2) { ListNode dummyHead = new ListNode(0); ListNode temp = dummyHead; while (head1 != null && head2 != null) { if (head1.val <= head2.val) { temp.next = head1; head1 = head1.next; } else { temp.next = head2; head2 = head2.next; } temp = temp.next; } if (head1 != null) { temp.next = head1; } else if (head2 != null) { temp.next = head2; } return dummyHead.next; } }
-
public class Solution { // 1.有交点时,设headA到交点距离为a,headB到交点距离为b,交点到链表尾距离为c。当nodeA遍历到链表尾后从headB开始遍历, // 当nodeB遍历到链表尾后从headA开始遍历,则nodeA与nodeB相遇时刚好在交点处,走过的距离都为a+b+c。 // 2.没交点时,设a链表长度为a,b链表长度为b,则最后nodeA和nodeB分别在B和A链表尾部变为Null;若a=b,则分别在A和B尾部变为null // 时间复杂度:O(m+n) 空间复杂度:O(1) public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { return null; } ListNode nodeA = headA; ListNode nodeB = headB; while (nodeA != nodeB) { nodeA = nodeA == null ? headB : nodeA.next; nodeB = nodeB == null ? headA : nodeB.next; } return nodeA; } } //leetcode submit region end(Prohibit modification and deletion) public class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } }
// 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 public class Solution { // 哈希表,时间复杂度:O(m+n),空间复杂度:O(m) public ListNode getIntersectionNode(ListNode headA, ListNode headB) { Set<ListNode> visitedSet = new HashSet<>(); while (headA != null) { visitedSet.add(headA); headA = headA.next; } while (headB != null) { if (visitedSet.contains(headB)) { return headB; } headB = headB.next; } return null; } } public class ListNode { int val; ListNode next; ListNode(int x) { val = x; next = null; } }
-
// 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 class Solution { public ListNode reverseList(ListNode head) { ListNode pre = null; ListNode curr = head; while (curr != null) { ListNode next = curr.next; curr.next = pre; pre = curr; curr = next; } return pre; } } //leetcode submit region end(Prohibit modification and deletion) public class ListNode { int val; ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
队列&栈
-
// 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。 class Solution { public boolean isValid(String s) { if (s.length() % 2 == 1) { // 若为奇数必不配对 return false; } Map<Character, Character> pairs = new HashMap<Character, Character>() {{ put(')', '('); put(']', '['); put('}', '{'); }}; Stack<Character> stack = new Stack<>(); for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (pairs.containsKey(ch)) { if (stack.isEmpty() || !stack.peek().equals(pairs.get(ch))) { return false; } stack.pop(); } else { stack.push(ch); } } return stack.isEmpty(); } }
-
class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode head = new ListNode(0); ListNode pre = head; while (l1 != null && l2 != null) { if (l1.val < l2.val) { pre.next = l1; l1 = l1.next; } else { pre.next = l2; l2 = l2.next; } pre = pre.next; } pre.next = l1 == null ? l2 : l1; return head.next; } }
-
//给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。 class Solution { public int largestRectangleArea(int[] heights) { int n = heights.length; int[] left = new int[n]; int[] right = new int[n]; Arrays.fill(right, n); // 栈中存放了坐标值。从栈底到栈顶,坐标值严格单调递增,同时对应的高度值也严格单调递增。 Stack<Integer> mono_stack = new Stack<>(); // 当枚举到第i根柱子时,我们从栈顶不断地移除height[j]≥height[i] 的j值。在移除完毕后,栈顶的j值就一定满足height[j]<height[i],此时j 就是i左侧且最近的小于其高度的柱子。 for (int i = 0; i < n; ++i) { while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) { // 当位置i被弹出栈时,说明此时遍历到的位置j的高度小于等于height[i],并且在j与i之间没有其他高度小于等于height[i] 的柱子。 // 因为如果在i和j之间还有其它位置的高度小于等于height[i]的,那么在遍历到那个位置的时候,i应该已经被弹出栈了。所以位置j就是位置i的右边界。 right[mono_stack.peek()] = i; mono_stack.pop(); } // 特殊情况,栈内元素空了,-1为哨兵,它是一根「虚拟」的、高度无限低的柱子 left[i] = (mono_stack.isEmpty() ? -1 : 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; } }
-
// 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 class Solution { 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]; Arrays.fill(down, m); Deque<Integer> stack = new LinkedList<>(); for (int i = 0; i < m; i++) { while (!stack.isEmpty() && left[stack.peek()][j] >= left[i][j]) { down[stack.peek()] = i; stack.pop(); } up[i] = stack.isEmpty() ? -1 : 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; } }
-
// 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 class MinStack { Deque<Integer> stack; Deque<Integer> minStack; /** * initialize your data structure here. */ public MinStack() { stack = new LinkedList<>(); minStack = new LinkedList<>(); 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(); } }
-
// 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 // 返回滑动窗口中的最大值。 class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; // 用优先队列。二元组 (num,index),表示元素num在数组中的下标为index。 PriorityQueue<int[]> pq = new PriorityQueue<>( // 先按值比较;值相同比较前后顺序 (pair1, pair2) -> pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1]); // 第一个窗口的值添加 for (int i = 0; i < k; ++i) { pq.offer(new int[] {nums[i], i}); } int[] ans = new int[n - k + 1]; ans[0] = pq.peek()[0]; for (int i = k; i < n; ++i) { pq.offer(new int[] {nums[i], i}); // 第一个元素不在窗口内时,弹出 while (pq.peek()[1] <= i - k) { pq.poll(); } ans[i - k + 1] = pq.peek()[0]; } return ans; } }
// 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 // 返回滑动窗口中的最大值。 class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; // 单调队列 Deque<Integer> deque = new LinkedList<>(); for (int i = 0; i < k; ++i) { while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) { // 从队列头把队列中小于待入栈元素的删除 deque.pollLast(); } // 放入队列尾部 deque.offerLast(i); } int[] ans = new int[n - k + 1]; for (int i = k; i < n; ++i) { while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) { // 从队列头把队列中小于待入栈元素的删除 deque.pollLast(); } // 放入队列尾部 deque.offerLast(i); while (deque.peekFirst() <= i - k) { // 当队首元素超出窗口,出队 deque.pollFirst(); } ans[i - k + 1] = nums[deque.peekFirst()]; } return ans; } }
-
// 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 // 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 // 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 class Solution { String src; int ptr; // 坐标 public String decodeString(String s) { src = s; ptr = 0; // 递归 return getString(); } public String getString() { if (ptr == src.length() || src.charAt(ptr) == ']') { // String -> EPS return ""; } char cur = src.charAt(ptr); int repTime = 1; String ret = ""; if (Character.isDigit(cur)) { // String -> Digits [ String ] String // 解析 Digits repTime = getDigits(); // 过滤左括号 ++ptr; // 解析 String String str = getString(); // 过滤右括号 ++ptr; // 构造字符串 while (repTime-- > 0) { ret += str; } } else if (Character.isLetter(cur)) { // String -> Char String // 解析 Char ret = String.valueOf(src.charAt(ptr++)); } return ret + getString(); } public int getDigits() { int ret = 0; // 计算多位数字 while (ptr < src.length() && Character.isDigit(src.charAt(ptr))) { ret = ret * 10 + src.charAt(ptr++) - '0'; } return ret; } }
// 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。 // 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。 // 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。 class Solution { int ptr; public String decodeString(String s) { LinkedList<String> stk = new LinkedList<>(); ptr = 0; while (ptr < s.length()) { char cur = s.charAt(ptr); if (Character.isDigit(cur)) { // 获取一个数字并进栈 String digits = getDigits(s); stk.addLast(digits); } else if (Character.isLetter(cur) || cur == '[') { // 获取一个字母并进栈 stk.addLast(String.valueOf(s.charAt(ptr++))); } else { // == ']' ++ptr; LinkedList<String> sub = new LinkedList<>(); while (!"[".equals(stk.peekLast())) { sub.addLast(stk.removeLast()); } Collections.reverse(sub); // 左括号出栈 stk.removeLast(); // 此时栈顶为当前 sub 对应的字符串应该出现的次数 int repTime = Integer.parseInt(stk.removeLast()); StringBuffer t = new StringBuffer(); String o = getString(sub); // 构造字符串 while (repTime-- > 0) { t.append(o); } // 将构造好的字符串入栈 stk.addLast(t.toString()); } } return getString(stk); } public String getDigits(String s) { StringBuffer ret = new StringBuffer(); while (Character.isDigit(s.charAt(ptr))) { ret.append(s.charAt(ptr++)); } return ret.toString(); } public String getString(LinkedList<String> v) { StringBuffer ret = new StringBuffer(); for (String s : v) { ret.append(s); } return ret.toString(); } }
-
// 请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。 // 如果气温在这之后都不会升高,请在该位置用 0 来代替。 class Solution { public int[] dailyTemperatures(int[] temperatures) { int length = temperatures.length; int[] ans = new int[length]; // 单调栈 Deque<Integer> stack = new LinkedList<>(); for (int i = 0; i < length; i++) { int tmp = temperatures[i]; while (!stack.isEmpty() && temperatures[stack.peek()] < tmp) { int index = stack.pop(); ans[index] = i - index; } stack.push(i); } return ans; } }
-
// 给定一个字符串 s,请你找出其中不含有重复字符的最长子串的长度。 class Solution { public int lengthOfLongestSubstring(String s) { // 哈希集合,记录每个字符是否出现过 Set<Character> set = new HashSet<Character>(); int n = s.length(); // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动 int right = -1; int ans = 0; for (int left = 0; left < n; left++) { if (left != 0) { // 左指针向右移动一格,移除一个字符 set.remove(s.charAt(left - 1)); } while (right + 1 < n && !set.contains(s.charAt(right + 1))) { // 不断地移动右指针 set.add(s.charAt(right + 1)); right++; } // 第 left 到 right 个字符是一个极长的无重复字符子串 ans = Math.max(ans, right - left + 1); } return ans; } }
哈希表
-
// 给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 class Solution { public List<List<String>> groupAnagrams(String[] strs) { Map<String, List<String>> map = new HashMap<>(); for (String str : strs) { char[] array = str.toCharArray(); Arrays.sort(array); String key = new String(array); List<String> list = map.getOrDefault(key, new ArrayList<>()); list.add(str); map.put(key, list); } return new ArrayList<>(map.values()); } }
-
// 给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。 class Solution { public int longestConsecutive(int[] nums) { Set<Integer> numSet = new HashSet<Integer>(); for (int num : nums) { numSet.add(num); } int result = 0; for (int num : numSet) { // 当它是连续序列中当第一个 if (!numSet.contains(num - 1)) { int currentNum = num; int currentLength = 1; while (numSet.contains(currentNum + 1)) { currentNum += 1; currentLength += 1; } result = Math.max(result, currentLength); } } return result; }
-
// 给定一个链表,判断链表中是否有环。 public class Solution { public boolean hasCycle(ListNode head) { Set<ListNode> set = new HashSet<>(); while (head != null) { // true if this set did not already contain the specified element if (!set.add(head)) { return true; } head = head.next; } return false; } }
-
class LRUCache { class DLinkedNode { int key; int value; DLinkedNode prev; DLinkedNode next; public DLinkedNode() { } public DLinkedNode(int _key, int _value) { key = _key; value = _value; } } private Map<Integer, DLinkedNode> cache = new HashMap<>(); private int size; private int capacity; private DLinkedNode head, tail; public 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; } }
二叉树
二叉搜索树
算法
模拟
贪心
- LeetCode 11. 盛最多水的容器
- LeetCode 714. 买卖股票的最佳时机含手续费
- LeetCode 122. 买卖股票的最佳时机 II
- LeetCode 55. 跳跃游戏
- LeetCode 309. 最佳买卖股票时机含冷冻期
- LeetCode 406. 根据身高重建队列
二分
递归&回溯
深度优先搜索DFS
宽度优先搜索BFS
分治
排序算法
Manacher算法
字典树Trie
动态规划
最短路算法
最小生成树
拓扑排序
并查集
- LeetCode 399. 除法求值
class Solution { public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) { int equationsSize = equations.size(); UnionFind unionFind = new UnionFind(2 * equationsSize); // 第 1 步:预处理,将变量的值与 id 进行映射,使得并查集的底层使用数组实现,方便编码 Map<String, Integer> hashMap = new HashMap<>(2 * equationsSize); int id = 0; for (int i = 0; i < equationsSize; i++) { List<String> equation = equations.get(i); String var1 = equation.get(0); String var2 = equation.get(1); if (!hashMap.containsKey(var1)) { hashMap.put(var1, id); id++; } if (!hashMap.containsKey(var2)) { hashMap.put(var2, id); id++; } unionFind.union(hashMap.get(var1), hashMap.get(var2), values[i]); } // 第 2 步:做查询 int queriesSize = queries.size(); double[] res = new double[queriesSize]; for (int i = 0; i < queriesSize; i++) { String var1 = queries.get(i).get(0); String var2 = queries.get(i).get(1); Integer id1 = hashMap.get(var1); Integer id2 = hashMap.get(var2); if (id1 == null || id2 == null) { res[i] = -1.0d; } else { res[i] = unionFind.isConnected(id1, id2); } } return res; } private class UnionFind { private int[] parent; /** * 指向的父结点的权值 */ private double[] weight; public UnionFind(int n) { this.parent = new int[n]; this.weight = new double[n]; for (int i = 0; i < n; i++) { parent[i] = i; weight[i] = 1.0d; } } public void union(int x, int y, double value) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) { return; } parent[rootX] = rootY; // 关系式的推导请见「参考代码」下方的示意图 weight[rootX] = weight[y] * value / weight[x]; } /** * 路径压缩到根结点,权制相乘 * * @param x * @return 根结点的 id */ public int find(int x) { if (x != parent[x]) { int origin = parent[x]; parent[x] = find(parent[x]); weight[x] *= weight[origin]; } return parent[x]; } public double isConnected(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) { return weight[x] / weight[y]; } else { return -1.0d; } } } }