数组 时间复杂度
操作
时间复杂度
查询 Lookup
O(1)
插入 Insert
O(n)
删除 Delete
O(n)
前插 Prepend/Pushfront
O(n)
后插 Append/Pushback
O(1)
删除有序数组中的重复项 LC26题目链接
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int removeDuplicates (int [] nums) { int cur = 0 ; for (int i = 1 ; i < nums.length; i++ ) { if (nums[i] != nums[cur]) nums[++cur] = nums[i]; } return cur+1 ; } }
移动零 LC283题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public void moveZeroes (int [] nums) { int cur = 0 ; for (int i = 0 ; i < nums.length; i ++) { if (nums[i] != 0 ) nums[cur++] = nums[i]; } while ( cur < nums.length ) { nums[cur] = 0 ; cur ++; } } }
合并两个有序数组 LC88题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public void merge (int [] nums1, int m, int [] nums2, int n) { int i = m -1 , j = n -1 , cur = m + n -1 ; while (i >= 0 || j >= 0 ) { if (j < 0 || (i >= 0 && nums1[i] >= nums2[j])){ nums1[cur] = nums1[i]; i--; } else { nums1[cur] = nums2[j]; j--; } cur--; } } }
变长数组ArrayList设计题 如何实现一个变长数组?
支持索引与随机访问
分配多长的连续空间
空间不够用了怎么办
空间剩余很多如何回收
链表 单链表和双链表数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class MyLinkedListNode { int val; MyLinkedListNode next; } class MyDoubleLinkedListNode { int val; MyDoubleLinkedListNode next; MyDoubleLinkedListNode pre; } public static void main (String[] args) { LinkedList protect = new LinkedList (0 , null ); LinkedList protectTail = new LinkedList (0 , null ); LinkedList protectHead = new LinkedList (0 , protectTail); protectTail.pre = proctedHead; protectedHead.pre = null ; }
时间复杂度
操作
时间复杂度
查询 Lookup
O(n)
插入 Insert
O(1)
删除 Delete
O(1)
前插 Prepend/Pushfront
O(1)
后插 Append/Pushback
O(1)
反转链表问题 LC206题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public ListNode reverseList (ListNode head) { ListNode last = null ; while (head != null ) { ListNode nextHead = head.next; head.next = last; last = head; head = nextHead; } return last; } }
K个一组翻转链表 LC25题目链接
解题思路
分组:每k个元素的头尾节点确定
子问题:针对每一组k个元素的反转问题
每组k个元素的前驱后继引用更新问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Solution { public ListNode reverseKGroup (ListNode head, int k) { ListNode protect = new ListNode (0 , head); ListNode last = protect; while (head != null ) { ListNode groupEnd = getEnd(head, k); if (groupEnd == null ) break ; ListNode cacheNexthead = groupEnd.next; reverseList(head, cacheNexthead); last.next = groupEnd; head.next = cacheNexthead; last = head; head = cacheNexthead; } return protect.next; } public ListNode getEnd (ListNode head, int k) { while (head != null ) { k--; if (k == 0 ) return head; head = head.next; } return head; } public void reverseList (ListNode head, ListNode nextHead) { ListNode last = head; head = head.next; while (head != nextHead) { ListNode cacheNextHead = head.next; head.next = last; last = head; head = cacheNextHead; } } }
邻值查找/双链表问题 ACWing题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 import java.util.Arrays;import java.util.Scanner;public class Main { public static void main (String args[]) { Scanner sc = new Scanner (System.in); int size = sc.nextInt(); int i = 0 ; Node[] nums = new Node [size], sorted = new Node [size]; int [] diff = new int [size-1 ], pre = new int [size-1 ]; while (i < size){ int number = sc.nextInt(); Node node = new Node (number, i); nums[i] = node; sorted[i] = node; i++; } Arrays.sort(sorted); for (int j = 0 ; j < sorted.length; j++){ if (j > 0 ) sorted[j].pre = sorted[j-1 ]; else sorted[j].pre = new Node (Integer.MIN_VALUE, -1 ); if (j < nums.length -1 ) sorted[j].post = sorted[j+1 ]; else sorted[j].post = new Node (Integer.MAX_VALUE, size); } while (i > 1 ){ i--; int preDiff = nums[i].pre.index >= 0 && nums[i].pre.index < size ? nums[i].value - nums[i].pre.value : Integer.MAX_VALUE; int postDiff = nums[i].post.index >= 0 && nums[i].post.index < size ? nums[i].post.value - nums[i].value: Integer.MAX_VALUE; diff[i-1 ] = Math.min(preDiff, postDiff); if (preDiff <= postDiff) pre[i-1 ] = nums[i].pre.index+1 ; else pre[i-1 ] = nums[i].post.index+1 ; removeNode(nums[i]); } print(diff, pre); } private static void print (int [] diff, int [] pre) { for (int i = 0 ; i < diff.length; i++){ System.out.println(diff[i] + " " + pre[i]); } } private static void removeNode (Node node) { Node postCache = node.post; node.pre.post = postCache; node.post.pre = node.pre; } static class Node implements Comparable <Node>{ int value; int index; public Node pre; public Node post; public Node (int value, int index) { this .value = value; this .index = index; } @Override public int compareTo (Node other) { return this .value - other.value; } } }
链表找环问题 LC141题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Solution { public boolean hasCycle (ListNode head) { ListNode fast = head; ListNode slow = head; do { if (fast == null || fast.next == null ) return false ; fast = fast.next.next; slow = slow.next; } while (fast != slow); return true ; } }
LC142题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Solution { public ListNode detectCycle (ListNode head) { ListNode fast = head; ListNode slow = head; do { if (fast == null || fast.next == null ) return null ; fast = fast.next.next; slow = slow.next; } while (fast != slow); ListNode slow2 = head; while (slow != slow2){ slow = slow.next; slow2 = slow2.next; } return slow; } }
合并两个有序链表 LC21题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public ListNode mergeTwoLists (ListNode list1, ListNode list2) { ListNode cur1 = list1, cur2 = list2, nextHead2 = null , protect = new ListNode (0 , list1),lastHead1 = protect; while (cur1 != null || cur2 != null ) { if (cur1 == null || (cur2 != null && cur1.val > cur2.val)){ nextHead2 = cur2.next; cur2.next = cur1; lastHead1.next = cur2; lastHead1 = cur2; cur2 = nextHead2; } else { lastHead1 = cur1; cur1 = cur1.next; } } return protect.next; } }
加一 LC66题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int [] plusOne(int [] digits) { return plusOne(digits, digits.length -1 ); } public int [] plusOne(int [] digits, int pos){ if (pos == -1 ) { int [] result = new int [digits.length+1 ]; result[0 ] = 1 ; for (int i = 0 ; i < digits.length; i ++){ result[i+1 ] = digits[i]; } return result; } digits[pos] += 1 ; if (digits[pos]/10 == 1 ) { digits[pos] %= 10 ; return plusOne(digits, pos-1 ); } return digits; } }
栈 后缀表达式求值 LC150题目链接
解题思路 建立一个用于存数的栈,逐一扫描后缀表达式中的元素。
如果遇到一个数,则把该数入栈。
如果遇到运算符,就取出栈顶的两个数进行计算,然后把结果入栈。 扫描完毕后,栈中恰好剩下的一个数,就是该表达式的值。
时间复杂度为O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int evalRPN (String[] tokens) { int cur = 0 ; Stack<Integer> nums = new Stack <>(); while (cur < tokens.length){ if (tokens[cur].equals("+" ) || tokens[cur].equals("-" ) || tokens[cur].equals("*" ) || tokens[cur].equals("/" )){ Integer operant1 = nums.pop(); Integer operant2 = nums.pop(); nums.push(calculate(operant1, operant2, tokens[cur])); }else nums.push(Integer.parseInt(tokens[cur])); cur ++; } return nums.peek(); } public Integer calculate (Integer x, Integer y, String operator) { switch (operator) { case "+" : return x + y; case "-" : return y - x; case "*" : return x * y; case "/" : return y/x; } return 0 ; } }
中缀表达式:基本计算器I & II LC227题目链接
解题思路一 中缀转后缀,然后计算:
需要看下一个符号的优先级是否高于本符号。
如果高于:需要一个操作符栈,低优先级符号先入栈,高优先级的符号后入栈,先出。
如果低于:把操作符栈出放入后缀表达式队列。
处理后缀表达式队列,参考上题。
3+2*2
运算符栈: + *
后缀表达式: 3 2 2 * +
3+2-2
运算符栈:
后缀表达式: 3 2 + 2 -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 class Solution { public int calculate (String s) { s += " " ; ArrayList<String> tokens = new ArrayList <>(); Stack<Character> operators = new Stack <>(); String number = "" ; boolean needsZero = true ; for (int i = 0 ; i < s.length(); i ++){ char ch = s.charAt(i); if (ch >= '0' && ch <= '9' ){ number += ch; needsZero = false ; continue ; } else if (!number.isEmpty()){ tokens.add(number); number = "" ; } if (ch == ' ' ) continue ; if (ch == '(' ) { operators.push('(' ); needsZero = true ; continue ; } if (ch == ')' ) { while (!operators.empty() && !operators.peek().equals('(' )){ tokens.add(operators.pop().toString()); } if (!operators.empty() && operators.peek().equals('(' )){ operators.pop(); } needsZero = false ; continue ; } int currRank = getRank(ch); while (!operators.empty() && getRank(operators.peek()) >= currRank){ tokens.add(operators.pop().toString()); } operators.push(ch); if (ch == '-' && needsZero) tokens.add(Integer.toString(0 )); } while (!operators.isEmpty()){ tokens.add(operators.pop().toString()); } return evalRPN(tokens); } public int getRank (char ch) { if (ch == '+' || ch == '-' ) return 1 ; if (ch == '*' || ch == '/' ) return 2 ; return 0 ; } int evalRPN (ArrayList tokens) { Stack<Integer> operants = new Stack <>(); for (int i = 0 ; i < tokens.size(); i ++ ){ String token = (String) tokens.get(i); if (token.equals("+" ) || token.equals("-" ) || token.equals("*" ) || token.equals("/" )){ Integer operant1 = operants.pop(); Integer operant2 = operants.pop(); operants.push(calc(operant1, operant2, token)); }else { operants.push(Integer.parseInt(token)); } } return operants.peek(); } Integer calc (Integer x, Integer y, String token) { if (token.equals("+" )) return x +y; if (token.equals("-" )) return y -x; if (token.equals("*" )) return x *y; if (token.equals("/" )) return y /x; return 0 ; } }
解题思路二 表达式拆分,加减运算为低优先级运算,将操作数和符号一起入栈。乘除为高优先级运算,遇到则立刻处理。最后将所有操作数出栈相加,得到结果。
LC224题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 class Solution { public int calculate (String s) { s += " " ; ArrayList<String> tokens = new ArrayList <>(); Stack<Character> operators = new Stack <>(); String number = "" ; boolean needsZero = true ; for (int i = 0 ; i < s.length(); i ++){ char ch = s.charAt(i); if (ch >= '0' && ch <= '9' ){ number += ch; needsZero = false ; continue ; } else if (!number.isEmpty()){ tokens.add(number); number = "" ; } if (ch == ' ' ) continue ; if (ch == '(' ) { operators.push('(' ); needsZero = true ; continue ; } if (ch == ')' ) { while (!operators.empty() && !operators.peek().equals('(' )){ tokens.add(operators.pop().toString()); } if (!operators.empty() && operators.peek().equals('(' )){ operators.pop(); } needsZero = false ; continue ; } int currRank = getRank(ch); while (!operators.empty() && getRank(operators.peek()) >= currRank){ tokens.add(operators.pop().toString()); } operators.push(ch); if (ch == '-' && needsZero) tokens.add(Integer.toString(0 )); } while (!operators.isEmpty()){ tokens.add(operators.pop().toString()); } return evalRPN(tokens); } public int getRank (char ch) { if (ch == '+' || ch == '-' ) return 1 ; if (ch == '*' || ch == '/' ) return 2 ; return 0 ; } int evalRPN (ArrayList tokens) { Stack<Integer> operants = new Stack <>(); for (int i = 0 ; i < tokens.size(); i ++ ){ String token = (String) tokens.get(i); if (token.equals("+" ) || token.equals("-" ) || token.equals("*" ) || token.equals("/" )){ Integer operant1 = operants.pop(); Integer operant2 = operants.pop(); operants.push(calc(operant1, operant2, token)); }else { operants.push(Integer.parseInt(token)); } } return operants.peek(); } Integer calc (Integer x, Integer y, String token) { if (token.equals("+" )) return x +y; if (token.equals("-" )) return y -x; if (token.equals("*" )) return x *y; if (token.equals("/" )) return y /x; return 0 ; } }
柱状图中最大的矩形 LC84题目链接
单调栈算法, 维护一个单调递增高度的栈,当一个新的高度低于当前栈顶时,单调性被破坏,需要将被破坏的栈元素逐个出栈,直到入栈新元素时单调性恢复。出栈元素的宽度元素叠加到accumulatedWidth中,以被后面的元素可以计算矩形面积。时间复杂度时O(n)。
思维套路
确定递增递减——关键在于考虑“前面不能影响到后面”的条件
本题中若h[i-1]>h[i], 则h[i-1]这个高度就无法影响到后面,自然可以单独计算了
代码套路
for每个元素
while(栈顶与新元素不满足单调性){弹栈,更新答案,累加“宽度”}
入栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int largestRectangleArea (int [] heights) { Stack<Rect> s = new Stack <>(); int result = 0 ; for (int i = 0 ; i <= heights.length; i ++){ int h = i == heights.length ? 0 : heights[i]; int accumulatedWidth = 0 ; while (!s.isEmpty() && s.peek().height > h) { accumulatedWidth += s.peek().width; result = Math.max(result, s.peek().height * accumulatedWidth); s.pop(); } s.push(new Rect (accumulatedWidth + 1 , h)); } return result; } class Rect { int width; int height; Rect (int width, int height){ this .width = width; this .height = height; } } }
接雨水 LC42题目链接
思维套路
确定递增递减——关键在于考虑“前面不能影响到后面”的条件
本题中若h[i-1]< h[i], 则h[i-1]这个高度就无法影响到后面,自然可以单独计算了
单调递减栈算法:维护一个单调递减的栈,当栈顶高度小于新入栈元素的高度时,弹出所有小于新入栈元素的元素,然后累计弹出元素的横条面积,其中横条面积是一个以栈顶为底和宽度,栈顶元素前后的最小值为高的长方形。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int trap (int [] heights) { int result = 0 ; for (int i = 0 ; i < heights.length; i++){ int accumulatedWidth = 0 ; int height = heights[i]; while (!s.isEmpty() && s.peek().height <= height) { int bottom = s.peek().height; accumulatedWidth += s.peek().width; s.pop(); int up = s.isEmpty() ? bottom : Math.min(height, s.peek().height); result += accumulatedWidth * (up - bottom); } s.push(new Rect ( accumulatedWidth + 1 , height )); } return result; } class Rect { int width; int height; Rect(int width, int height){ this .width = width; this .height = height; } } Stack<Rect> s = new Stack <>(); }
有效的括号 LC20题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public boolean isValid (String s) { for (int i = 0 ; i < s.length(); i++){ char c = s.charAt(i); if (c == ')' ){ if (o.isEmpty() || !cleanUp("(" )) return false ; }else if (c == '}' ){ if (o.isEmpty() || !cleanUp("{" )) return false ; }else if (c == ']' ){ if (o.isEmpty() || !cleanUp("[" )) return false ; }else o.push(String.valueOf(c)); } if (o.isEmpty()) return true ; return false ; } private boolean cleanUp (String match) { while (!o.isEmpty()) { String x = o.pop(); if (x.equals(match)) return true ; else return false ; } return false ; } Stack<String> o = new Stack <>(); }
最小栈 LC155题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class MinStack { public MinStack () { } public void push (int val) { if (min.isEmpty() || (!min.isEmpty() && min.peek() >= val)) { min.push(val); } s.push(val); } public void pop () { if (s.pop().equals(min.peek())) min.pop(); } public int top () { return s.peek(); } public int getMin () { return min.peek(); } private Stack<Integer> min = new Stack <>(); private Stack<Integer> s = new Stack <>(); }
队 滑动窗口最大值 LC139题目链接
思维套路
单调队列维护的是一个候选集和,前面的较旧,后边比较新(时间有单调性)
候选项的某个属性也具有单调性(比如整数值)
确定递增递减的方法——考虑任意连个候选项j1< j2,写出j1比j2优的条件
排除冗余的关键:若j1比j2差(nums[j1]< nums[j2],且j1的生命周期比j2短,那j1就没有留在队列中的意义。
单调递减队列:需要维护一个单调递减队列,队尾插入新元素,当新元素大于队尾元素时,队尾元素出栈,直到满足单调条件。队首元素需要满足索引大于i-k条件。
代码套路
for每个元素
while(队头过期) 队头出队
取队头为最佳选项,计算答案
while(队尾与新元素不满足单调性)队尾出队
新元素入队
2和3的顺序取决于i是不是候选项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int [] maxSlidingWindow(int [] nums, int k) { int [] result = new int [Math.max(nums.length - k + 1 , 1 )]; int cur = 0 ; for (int i = 0 ; i < nums.length; i ++){ int number = nums[i]; while (!q.isEmpty() && nums[q.peekFirst()] <= i - k) q.removeFirst(); while (!q.isEmpty() && nums[q.peekLast()] < number) q.removeLast(); q.add(i); if (i >= k-1 ) result[cur++] = nums[q.peekFirst()]; } return result; } LinkedList<Integer> q = new LinkedList <>(); }
设计循环双端队列 LC641题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class MyCircularDeque { public MyCircularDeque (int k) { q = new LinkedList <Integer>(); capacity = k; } public boolean insertFront (int value) { if (q.size() == capacity) return false ; q.addFirst(value); return true ; } public boolean insertLast (int value) { if (q.size() == capacity) return false ; q.addLast(value); return true ; } public boolean deleteFront () { if (q.isEmpty()) return false ; q.removeFirst(); return true ; } public boolean deleteLast () { if (q.isEmpty()) return false ; q.removeLast(); return true ; } public int getFront () { return q.isEmpty()? -1 : q.peekFirst(); } public int getRear () { return q.isEmpty()? -1 : q.peekLast(); } public boolean isEmpty () { return q.isEmpty(); } public boolean isFull () { return q.size() == capacity; } private LinkedList<Integer> q ; private int capacity; }
最大矩形 LC85题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public int maximalRectangle (char [][] matrix) { int rows = matrix.length, cols = matrix[0 ].length; int [][] S = new int [rows+1 ][cols]; int [] max = new int [rows]; int ans = 0 ; for (int r = 0 ; r <= rows; r ++) { for (int c = 0 ; c < cols; c++) { if ( r == 0 || matrix[r-1 ][c] == '0' ) { S[r][c] = 0 ; continue ; } else S[r][c] = S[r-1 ][c]+1 ; } } for (int r = 1 ; r <= rows; r++) { max[r-1 ] = largestRectangleArea(S[r]); ans = Math.max(ans, max[r-1 ]); } return ans; } public int largestRectangleArea (int [] heights) { Stack<Rect> s = new Stack <>(); int result = 0 ; for (int i = 0 ; i <= heights.length; i ++){ int h = i == heights.length ? 0 : heights[i]; int accumulatedWidth = 0 ; while (!s.isEmpty() && s.peek().height > h) { accumulatedWidth += s.peek().width; result = Math.max(result, s.peek().height * accumulatedWidth); s.pop(); } s.push(new Rect (accumulatedWidth + 1 , h)); } return result; } class Rect { int width; int height; Rect (int width, int height){ this .width = width; this .height = height; } } }
前缀和 前缀和思想是运用前缀和数组作为桥梁,取解决数组的各种子数组优化问题。已知数组A[ i],
s[ i] = s[ i - 1] + A[ i]
字段和从l 到 r则表达为:
sum(l, r) = s[ r] - s[ l - 1]
当A中都是非负数时,前缀和数组s单调递增。这样子数组和问题就转化成了s数组元素的问题。
统计优美子数组 LC1248题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int numberOfSubarrays (int [] nums, int k) { int [] s = new int [nums.length+1 ]; s[0 ] = 0 ; int [] count = new int [nums.length+1 ]; count[0 ] = 1 ; for (int i = 1 ; i < count.length; i ++) count[i] = 0 ; for (int i = 1 ; i <= nums.length; i++) { s[i] = s[i-1 ] + nums[i-1 ]%2 ; count[s[i]]++; } int result = 0 ; for (int i = 0 ; i < s.length; i ++ ) { if (s[i] >=k ) result += count[s[i] - k]; } return result; } }
最大子序和 LC53题目链接
解题思路一 通过维护前缀和数组,和前缀最小和数组,两者差为子数组和,求最大值问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int maxSubArray (int [] nums) { int [] s = new int [nums.length+1 ]; s[0 ]=0 ; int [] preMin = new int [nums.length+1 ]; preMin[0 ] = 0 ; for (int i = 0 ; i < nums.length; i++) { s[i+1 ] = s[i] + nums[i]; } int result = -10000 ; for (int i = 1 ; i < s.length; i ++) { preMin[i] = Math.min(preMin[i -1 ], s[i]); result = Math.max(result, s[i] - preMin[i-1 ]); } return result; } }
解题思路二 通过贪心算法,维护双指针,当子数组和为负数,立即舍弃,慢指针前进,否则快指针已知前进直到数组尾。
二维前缀和 二维数组A (m*n),其前缀和s[i]s[j]为第i行第j列元素左上角、子矩阵的各个元素之和。
递推关系
s[i][j]= s[i-1][j] +s[i][j-1] - s[i-1]s[j-1] + A[i][j]
子矩阵和
以[p][q]为左上角元素,[i][j]为右下角元素的子矩阵的元素和。
递推关系
sum(p,q,i,j) = s[i][j] +s[i][q-1] - s[p-1]s[j] + s[p-1][q-1]
其中,p,q总是需要减一。
二位区域和检索——矩阵不可变 LC304题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class NumMatrix { public NumMatrix (int [][] matrix) { n = matrix.length; m = matrix[0 ].length; sum = new int [n+1 ][m+1 ]; for (int i = 1 ; i <= n ; i ++){ for (int j = 1 ; j <= m; j++) { sum[i][j] = sum[i - 1 ][j] + sum[i][j-1 ] -sum[i-1 ][j-1 ]+ matrix[i-1 ][j-1 ]; } } } public int sumRegion (int row1, int col1, int row2, int col2) { row1++; row2++; col1++; col2++; return sum[row2][col2] - sum[row1-1 ][col2] - sum[row2][col1-1 ] + sum[row1-1 ][col1-1 ]; } int sum[][]; int n, m; }
差分 差分数组B是由数组的元素差组成的,其中第i元素等于原数组A第i元素与第i-1元素或0的差。
递推关系
B[1] = A[1]
B[i] = A[i -1]
差分数组的前缀和就是原数组
把A数组的第l到第r元素均加d,对应的B变化为B[l]加d,B[r+1]减d。
航班预定统计 LC1109题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public int [] corpFlightBookings(int [][] bookings, int n) { int [] delta = new int [n+2 ]; for (int i = 0 ; i < n+2 ; i ++) delta[i] = 0 ; for (int i = 0 ; i < bookings.length; i++){ int [] booking = bookings[i]; int first = booking[0 ]; int last = booking[1 ]; int seats = booking[2 ]; delta[first] += seats; delta[last+1 ] += -seats; } int [] sum = new int [n]; for (int i = 0 ;i < n; i++) sum[i] = delta[1 ]; for (int i = 1 ; i < n; i++){ sum[i] = sum[i-1 ] + delta[i+1 ]; } return sum; } }
双指针扫描 两数之和II —— 输入有序数组 LC167题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int [] twoSum(int [] numbers, int target) { int j = numbers.length -1 ; for (int i = 0 ; i < numbers.length; i++) { while (i < j && numbers[i] + numbers[j] > target) j--; if (i < j && numbers[i] + numbers[j] == target) { return new int []{i+1 , j+1 }; } } return new int []{}; } }
两数之和 LC1题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int [] twoSum(int [] nums, int target) { Pair[] sorted = new Pair [nums.length]; for (int i = 0 ; i < nums.length; i ++) sorted[i]= new Pair (i, nums[i]); Arrays.sort(sorted); int j = nums.length-1 ; for (int i = 0 ; i < nums.length; i++){ while (i < j && sorted[i].value + sorted[j].value > target) j--; if (i < j && sorted[i].value + sorted[j].value == target) return new int []{sorted[i].index, sorted[j].index}; } return new int []{}; } static class Pair implements Comparable <Pair> { public final int index; public final int value; public Pair (int index, int value) { this .index = index; this .value = value; } @Override public int compareTo (Pair other) { return Integer.valueOf(this .value).compareTo(other.value); } } }
三数之和 LC15题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public List<List<Integer>> threeSum (int [] nums) { Arrays.sort(nums); List<List<Integer>> result = new ArrayList <>(); for (int i = 0 ; i < nums.length; i++){ if (i != 0 && nums[i] == nums[i-1 ]) continue ; List<List<Integer>> twoSumResults = twoSum(nums, i + 1 , -nums[i]); for (List<Integer> twoSumResult : twoSumResults){ result.add(Arrays.asList(nums[i], twoSumResult.get(0 ), twoSumResult.get(1 ))); } } return result; } List<List<Integer>> twoSum (int [] nums, int start, int target) { List<List<Integer>> result = new ArrayList <>(); int j = nums.length-1 ; for (int i = start; i < nums.length; i++){ if (i != start && nums[i] == nums[i-1 ]) continue ; while (i < j && nums[i] + nums[j] > target) j--; if (i < j && nums[i] + nums[j] == target) { List<Integer> finding = new ArrayList <>(); finding.add(nums[i]); finding.add(nums[j]); result.add(finding); } } return result; } }
盛最多水的容器 LC11题目链接
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int maxArea (int [] height) { int i = 0 , j = height.length-1 , ans = 0 ; while ( i < j){ ans = Math.max(ans, Math.min(height[i], height[j])* (j-i)); if (height[i] < height[j]) i++; else j--; } return ans; } }
递归 递归的三个关键:
定义需要递归的问题(重叠子问题)—— 数学归纳法思想
确定递归边界
保护与还原现场
递归实现的“暴力搜索”或者叫枚举、回溯方法的基本复杂度为:
递归形式
时间复杂度规模
问题举例
指数型
k^n
子集、大体积背包
排列型
n!
全排列、旅行商、N皇后
组合型
n!/(m!(n-m)!)
组合选数
子集 LC78题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public List<List<Integer>> subsets (int [] nums) { n = nums.length; recur(nums,0 ); return ans; } void recur (int [] nums, int i) { if (i == n) { ans.add(new ArrayList <>(chosen)); return ; } recur(nums, i+1 ); chosen.add(nums[i]); recur(nums, i+1 ); chosen.remove(chosen.size()-1 ); } private int n; List<Integer> chosen = new ArrayList <>(); List<List<Integer>> ans = new ArrayList <>(); }
组合 LC77题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public List<List<Integer>> combine (int n, int k) { this .n = n; this .k = k; recur(1 ); return ans; } void recur ( int i) { if (chosen.size() > k || chosen.size() + (n -i + 1 ) <k ) return ; if (i == n+1 ) { if (chosen.size() == k) ans.add(new ArrayList <>(chosen)); return ; } recur(i+1 ); chosen.add(i); recur(i+1 ); chosen.remove(chosen.size()-1 ); } private int n, k; List<Integer> chosen = new ArrayList <>(); List<List<Integer>> ans = new ArrayList <>(); }
全排列 LC46题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public List<List<Integer>> permute (int [] nums) { this .n = nums.length; this .used = new boolean [n]; recur(nums, 0 ); return ans; } void recur (int [] nums, int pos) { if (pos == n) { ans.add(new ArrayList <>(a)); return ; } for (int i = 0 ; i < n; i++) { if (!used[i]){ a.add(nums[i]); used[i] = true ; recur(nums, pos +1 ); used[i] = false ; a.remove(a.size()-1 ); } } } boolean [] used; List<Integer> a = new ArrayList <>(); int n; List<List<Integer>> ans = new ArrayList <>(); }
全排列II LC47题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public List<List<Integer>> permuteUnique (int [] nums) { this .n = nums.length; this .used = new boolean [n]; Arrays.sort(nums); recur(nums, 0 ); return ans; } void recur (int [] nums, int pos) { if (pos == n) { ans.add(new ArrayList <>(a)); return ; } for (int i = 0 ; i < n; i++) { if (used[i] || (i > 0 && nums[i] == nums[i - 1 ] && !used[i - 1 ])) { continue ; } a.add(nums[i]); used[i] = true ; recur(nums, pos +1 ); used[i] = false ; a.remove(a.size()-1 ); } } boolean [] used; List<Integer> a = new ArrayList <>(); int n, first; List<List<Integer>> ans = new ArrayList <>(); Map<Integer, Boolean> record = new HashMap <>(); }
树 反转二叉树 LC226题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public TreeNode invertTree (TreeNode root) { if (root == null ) return null ; TreeNode cache = root.left; root.left = root.right; root.right = cache; invertTree(root.left); invertTree(root.right); return root; } }
验证二叉搜索树 LC98题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public boolean isValidBST (TreeNode root) { return isValidBST(root, Integer.MIN_VALUE, Integer.MAX_VALUE); } boolean isValidBST (TreeNode root, long low, long up) { if (root == null ) return true ; if (root.val > up || root.val < low) return false ; return isValidBST(root.left, low, (long )root.val -1 ) && isValidBST(root.right, (long )root.val + 1 , up); } }
二叉树的最大、最小深度 LC104题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int maxDepth (TreeNode root) { if (root == null ) return 0 ; return Math.max(maxDepth(root.left), maxDepth(root.right)) +1 ; } }
LC111题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int minDepth (TreeNode root) { if (root == null ) return 0 ; if (root.left == null ) return minDepth(root.right)+1 ; if (root.right == null ) return minDepth(root.left)+1 ; return Math.min(minDepth(root.left), minDepth(root.right)) +1 ; } }
分治 把原问题划分成若干个同类子问题,分别解决后,再把结果合并起来。
关键点: 原问题和各个子问题都是重复的(同类的)——递归定义 除了向下递归“问题”,还要向上合并“结果”
分治算法一般用递归实现,分为三步骤:
递归边界定义
split:分解成多个子问题
merge:合并子问题结果
Pow(x,n) LC50题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public double myPow (double x, int n) { if (n == 0 ) return 1 ; if (n == Integer.MIN_VALUE) return 1.0 / (myPow(x, -(n + 1 )) *x); if (n < 0 ) return 1.0 / myPow(x, -n); double temp = myPow(x, n/2 ); double ans = temp * temp; if (n%2 == 1 ) ans *= x; return ans; } }
括号生成 LC22题目链接
组合型递归问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Solution { public List<String> generateParenthesis (int n) { List<String> ans = new ArrayList <>(); if (n == 0 ) return Arrays.asList(new String []{"" }); if (store.containsKey(n)) return store.get(n); for (int k = 1 ; k <= n; k++ ){ List<String> A = generateParenthesis(k-1 ); store.put(k-1 , A); List<String> B = generateParenthesis(n-k); store.put(n-k, B); for (String a : A ){ for (String b: B) { ans.add("(" + a + ")" + b); } } } return ans; } Map<Integer, List<String>> store = new HashMap <>(); }
搜索二维矩阵 LC74题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public boolean searchMatrix (int [][] matrix, int target) { int i = 0 ; for ( ;i < matrix.length; i++) { if (target >= matrix[i][0 ] && ( i == matrix.length -1 || target < matrix[i+1 ][0 ])) break ; } for (int j = 0 ; j < matrix[0 ].length; j++) { if (i < matrix.length && target == matrix[i][j]) return true ; } return false ; } }
合并K个升序链表 LC23题目链接
拆分问题,先合成两个k/2组成的链表,把这两个链表合成k个组成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Solution { public ListNode mergeKLists (ListNode[] lists) { return merge(lists, 0 , lists.length -1 ); } public ListNode merge (ListNode[] lists, int start, int end) { if ( start == end) return lists[start]; if ( start > end ) return null ; int mid = (start + end) /2 ; return mergeTwoLists(merge(lists, start, mid), merge(lists, mid +1 , end)); } public ListNode mergeTwoLists (ListNode list1, ListNode list2) { ListNode cur1 = list1, cur2 = list2, nextHead2 = null , protect = new ListNode (0 , list1),lastHead1 = protect; while (cur1 != null || cur2 != null ) { if (cur1 == null || (cur2 != null && cur1.val > cur2.val)){ nextHead2 = cur2.next; cur2.next = cur1; lastHead1.next = cur2; lastHead1 = cur2; cur2 = nextHead2; } else { lastHead1 = cur1; cur1 = cur1.next; } } return protect.next; } }
为运算表达式设计优先级 LC241题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 class Solution { static final int ADDITION = -1 ; static final int SUBTRACTION = -2 ; static final int MULTIPLICATION = -3 ; public List<Integer> diffWaysToCompute (String expression) { ArrayList<Integer> ops = new ArrayList <>(); for (int i = 0 ; i < expression.length();) { if (expression.charAt(i) == '+' ){ ops.add(ADDITION); }else if (expression.charAt(i) == '-' ){ ops.add(SUBTRACTION); }else if (expression.charAt(i) == '*' ){ ops.add(MULTIPLICATION); }else { int number = 0 ; while (i < expression.length() && expression.charAt(i) <= '9' && expression.charAt(i) >= '0' ){ number = 10 * number + expression.charAt(i) - '0' ; i++; } ops.add(number); continue ; } i++; } List<Integer>[][] dp = new List [ops.size()][ops.size()]; for (int i = 0 ; i < ops.size(); i++) { for (int j = 0 ; j < ops.size(); j++) { dp[i][j] = new ArrayList <Integer>(); } } return dfs(dp, 0 , ops.size() -1 , ops); } public List<Integer> dfs (List<Integer>[][] dp, int start, int end, List<Integer> ops) { if ( start > end) return new ArrayList <>(); if (!dp[start][end].isEmpty()) return dp[start][end]; if ( start == end) { dp[start][end].add(ops.get(start)); return Arrays.asList(ops.get(start)); } for (int mid = start; mid < end; mid+=2 ){ List<Integer> leftResults = dfs(dp, start, mid, ops); List<Integer> rightResults = dfs(dp, mid+2 , end, ops); for (Integer left : leftResults){ for (Integer right : rightResults){ if (ops.get(mid+1 ) == ADDITION) dp[start][end].add(left + right); if (ops.get(mid+1 ) == SUBTRACTION) dp[start][end].add(left - right); if (ops.get(mid+1 ) == MULTIPLICATION) dp[start][end].add(left * right); } } } return dp[start][end]; } }
给表达式添加运算符 LC282题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Solution { List<String> ans = new ArrayList <>(); int n, target; String num; public List<String> addOperators (String num, int target) { this .n = num.length(); this .target = target; this .num = num; long sum = 0 , pre = 0 ; dfs(0 , 0 , "" , 0 ); return ans; } private void dfs (long sum, long pre, String exp, int start) { if (start == n) { if (sum == target) ans.add(exp); return ; } long val = 0 ; for (int i = start; i < n; i++) { if (i != start && num.charAt(start) == '0' ) break ; val = val* 10 + num.charAt(i) - '0' ; if (start == 0 ) { dfs(val, val, exp + val, i+1 ); }else { dfs(sum + val, val, exp + "+" + String.valueOf(val), i+1 ); dfs(sum - val, -val, exp + "-" + String.valueOf(val), i+1 ); dfs(sum - pre + pre * val, pre * val, exp + "*" + String.valueOf(val), i+1 ); } } } }
数组中的第K个最大元素 LC215题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int findKthLargest (int [] nums, int k) { Pair[] sorted = new Pair [nums.length]; for (int i = 0 ; i < nums.length ; i++) sorted[i] = new Pair (i, nums[i]); Arrays.sort(sorted); return sorted[k-1 ].value; } static class Pair implements Comparable <Pair>{ int index, value; public Pair (int index, int value) { this .index = index; this .value = value; } @Override public int compareTo (Pair other) { return other.value - this .value; } } }
综合习题 和为 K 的子数组 LC560题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int subarraySum (int [] nums, int k) { int [] s = new int [nums.length+1 ]; s[0 ] = 0 ; Map<Integer, Integer> count = new HashMap <>(); count.put(0 , 1 ); int result = 0 ; for (int i = 1 ; i <= nums.length; i++) { s[i] = s[i-1 ] + nums[i-1 ]; result += count.getOrDefault(s[i] - k, 0 ); count.put(s[i], count.getOrDefault(s[i], 0 )+1 ); } return result; } }
最接近原点的 K 个点 LC973题目链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class Solution { public int [][] kClosest(int [][] points, int k) { sorted = new Pair [points.length]; for (int i = 0 ; i < points.length; i++) { enqueue(points, i); } Arrays.sort(sorted); int [][] result = new int [k][2 ]; int current = 0 ; for (int j = 0 ; j < k;j++){ result[current][0 ] = points[sorted[j].index][0 ]; result[current][1 ] = points[sorted[j].index][1 ]; current ++; } return result; } private void enqueue (int [][] points, int index) { int distance = getDistance(points, index); sorted[index] = new Pair (index, distance); } private int getDistance (int [][] points, int index) { return points[index][0 ]*points[index][0 ] + points[index][1 ]*points[index][1 ]; } LinkedList<Integer> q = new LinkedList <>(); Pair[] sorted; static class Pair implements Comparable <Pair> { public final int index; public final int value; public Pair (int index, int value) { this .index = index; this .value = value; } @Override public int compareTo (Pair other) { return Integer.valueOf(this .value).compareTo(other.value); } } }
鸡蛋掉落 LC887题目链接
解题思路:
回溯:递归展开,dp归纳法得出最优解
记忆化剪枝优化
根据其单调性二分查找快速定位最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 class Solution { public int superEggDrop (int k, int n) { return recur(k, n); } private int recur (int K, int N) { if (K == 1 || N == 0 || N == 1 ) { return N; } int key = 1000 *N +K; if (memo2.containsKey(key)) return memo2.get(key); int low = 1 , high = N; int X = 0 ; while (low +1 < high){ X = (low + high)/2 ; int KNlow = recur(K-1 , X-1 ); int KNhigh = recur(K, N-X); if (KNlow < KNhigh) low = X; else if (KNlow > KNhigh) high = X; else low = high = X; } int KN = Math.max( recur(K-1 , low-1 ), recur(K, N-low) ); int KN2 = Math.max(recur(K-1 , high-1 ), recur(K, N-high)); int min = Math.min(KN, KN2)+1 ; memo2.put(key, min); return min; } Map<Integer, Integer> memo2 = new HashMap <>(); }