重拾数据结构
生活总会有一些烦心事,冷静自己,重温些简单的知识,希望可以有新的理解,让自己不要胡思乱想。学习的知识包括Java的基础部分,参考书籍《Java核心技术》卷一 / 卷二;以及以下提到的数据结构( 极客时间:算法面试通过)。
下图是区块链的一个结构,可以很容易看出上面是一个基础的单向链表结构,下面就是二叉树,这是我意想不到的简单结构,有兴趣就多看了两眼。
精通一个领域
-Chunk it up (切碎知识点)
-Deliberate practicing(刻意练习)
-Feedback(反馈)
切题四件套
-Clarification
-Possible solutions ( compare:time/space; optimal加强)
-Coding 多写
-Test cases
算法与数据结构
Big O notation
无聊的话可以去LeetCode做做题,看看别人的解题思路。
1 数组&链表
Array 数组
查找数组中的某个元素:O(1)
插入/删除:平均是O(n)—最幸运的是在最后一位操作那就是O(1)
Linked List 链表
插入/删除:O(1),查找O(n)
Doubly Linked List 双向链表
插入/删除,append,prepend:O(1),space / lookupO(n)
反转一个单链表&判断链表是否有环
反转一个单链表:https://leetcode-cn.com/problems/reverse-linked-list/
// 迭代
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归
public ListNode reverseList(ListNode head) {
// 1. 递归终止条件
if (head == null || head.next == null) {
return head;
}
ListNode p = reverseList(head.next);
head.next.next = head;
head.next = null;
return p;
}
两两交换链表中的节点:https://leetcode-cn.com/problems/swap-nodes-in-pairs/
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
}
环形链表:https://leetcode-cn.com/problems/linked-list-cycle/
https://leetcode-cn.com/problems/linked-list-cycle-ii/
K 个一组翻转链表:https://leetcode-cn.com/problems/reverse-nodes-in-k-group/
判断链表是否有环:1.强行硬做,循环检测0.5s / 1s是否到的了结尾;2. set集合判重O(n*1);3.快慢指针:快指针走两步,满指针走一步,最后判断相遇O(n)
2 栈 Stack、队列Queue
Stack: FILO
Queue: FIFO
判断括号字符串是否有效
String,大、中、小括号=> 合法?eg: “()”,"()[]","([)]"
可以利用栈来解决这个问题:
- 左括号 push
- 右括号 peek -> pop
- 最后判断stack 是否empty
Time:O(1)*n -> O(n)
Space:O(n)
def isValid(self, s):
stack = []
paren_map = {')' : '(', ']' : '[', '}' : '{'}
for c in s:
// 不是右括号 ) ] }
if c not in paren_map:
stack.append(c)
elif not stack or paren_map[c] != stack.pop():
return False
return not stack
public booolean isValid(String s) {
int length;
do {
length = s.length();
s = s.replace("()" , "").replace("{}", "").replace("[]", "");
} while(length != s.length());
return s.length() == 0;
}
// 代码简洁易懂,但是时间复杂度不如栈来的妙
用队列实现栈&用栈实现队列
Stack => Qunue
push, pop, peek(查看队列最后一个元素)
输入栈 / 输出栈
输入栈的数据到了输出栈,就清空输入栈,好让后面的元素接着进来。
Qunue => Stack
3 优先队列
PriorityQueue 优先队列
正常入,按优先级出。
实现机制:
- 堆Heap( Binary, Binomial, Fibonacci)
- Binary Search Tree
小顶堆 Mini Heap(父节点比左/右孩子小)
大顶堆
LeetCode703:返回数据流中的第K大元素
- 保存前K个最大值 => sorted,NKlogK
- 维护小顶堆,size=K,下一个数据进来,比顶点小,就不用管,比顶点大,就要踢掉原来的小顶点log2K
5.10更新:
LeetCode239:返回滑动窗口中的最大值
1,3,-1,-3,5,3,6,k=3
1.MaxHeap
维护Heap -> logK;Max -> 堆顶元素,N*logK
2.Queue => 双端队列deque
入队列;维护 O(N)
result->3,3,5,5,6
4 哈希表
哈希碰撞,解决方法:
List & Map & Set
list_x = [1,2.3]
map_x = {‘jack’:100, ‘tom’:200}
set_x = {‘jack’, ‘tom’, ‘andy’}
HashMap & TreeMap
HashSet & TreeSet
O(1) O(log2N)
(hashtable vs binary-search-tree)
相对快一点 有序
LeetCode242:有效的字母异位词
“rat” , “car” -> false
“cat”, “atc” -> true
1.sort:String => sorted
快排:O(N logN)
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1);
Arrays.sort(str2);
return Arrays.equals(str1, str2);
}
}
//作者:LeetCode-Solution
2.Map计数:{letter: count}
O(N)
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] table = new int[26];
for (int i = 0; i < s.length(); i++) {
table[s.charAt(i) - 'a']++;
}
for (int i = 0; i < t.length(); i++) {
table[t.charAt(i) - 'a']--;
if (table[t.charAt(i) - 'a'] < 0) {
return false;
}
}
return true;
}
}
class Solution {
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> table = new HashMap<Character, Integer>();
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
table.put(ch, table.getOrDefault(ch, 0) + 1);
}
for (int i = 0; i < t.length(); i++) {
char ch = t.charAt(i);
table.put(ch, table.getOrDefault(ch, 0) - 1);
if (table.get(ch) < 0) {
return false;
}
}
return true;
}
}
//作者:LeetCode-Solution
两数之和&三数之和
两数之和:
[2, 7, 11,15], 9 -> 返回下标 [0,1]
1.暴力求解 O(N^2)
[X,Y] -> X+Y = 9
2.set: x+y=9 => y=9-x
for: x:0->length O(N)
set: 9-x O(1)
O(N)
三数之和:
[-1, 0, 1, 2, -1, -4], 0 -> [-1,0,1]
1.暴力求解:3层循环 O(N^3)
2.c = -(a+b) => set : O(1)
枚举a,b,2层循环,set中查找-(a+b)
O(N^2)
3.sort. find:整个数组排序O(N logN)
[-4, -1, -1, 0, 1, 2]
5.11更新:
5 树&二叉树&二叉搜索树
Linked List 就是特殊化的Tree
Tree 就是特殊化的Graph
树
二叉搜索树(Binary Search Tree):有序二叉树,排序二叉树,是指一棵空树或者具有下列性质的二叉树:
1.左子树上所有结点的值均小于它的根结点的值;
2.右子树上所有结点的值均大于它的根结点的值;
3.Recursively,左/右子树也分别为二叉查找树。
图
LeetCode98:验证二叉搜索树
1.In-order => array升序,O(N)
2.递归Recursion: validate(…,min,max),O(N)
5.13更新:
LeetCode235/236:二叉树&二叉搜索树的最近公共祖先
1.path1, path2 => LCA
O(N)
2.Recursion:find P or Q(root, p, q)
if root == p or ==q: return root
find P or Q(root left p q)
find P or Q(…right p q)
O(N)
二叉树遍历
- pre-order 前序:根-左-右
ABDECFG - in-order 中序:左-根-右
DBEAFCG - post-order 后序:左-右-根
DEBFGCA
6 递归&分治
递归——循环:通过函数体来进行的死循环
举个例,电影《盗梦空间》,
计算n! = 123*…*n
def Factorial(n):
if n <= 1:
return 1
return n*Factorial(n-1)
斐波拉契
递归的高阶:分治(大问题分成小问题,最后把结果一层一层返回)
Pow(x,n)&求众数
xn
n>>=1 => n右移一位 n = n / 2
求众数
[1,3,3,2,3], count(x) > n/2
1.暴力:双循环,loop:x loop count(x) -----> O(N2)
2.Map:{x: count_x} ; loop => Map count ------> O(N)
3.sort:[1,2,3,3,3] 计算重复次数,大于2/n就输出-----> O(N logN)
4.分治算法:[1,1,1,0,2]一分为二,left==right -> left
return count(left) > count(right)
-----> O(N logN)
5.14更新:
7 贪心算法
贪心法,贪婪算法。在对问题求解时,总是做出在当前看来是最好的选择。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解,这种子问题最优解成为最优子结构。
在部分的情况下使用,很多情况都不适用。
贪心算法和动态规划的不同在于他对每个子问题的解决方案都做出选择,不能回退。动态规则则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。
5.18更新:
买卖股票的最佳时机
持有1股,每天买卖无数次,无交易手续费
[7,1,5,3,6,4] ->7
[1,2,3,4,5] -> 4
[7,6,3,2] -> 0
1.DFS: O(2n)
2.贪心算法:O(N),买卖无数次
3.DP动态规划:O(N)复杂强大
8 广度优先搜索BFS
使用队列,判重的set避免有环线
9 深度优先搜索DFS
二叉树层次遍历
1.BFS
2.DFS
104,111:二叉树最大/最小深度
22:生成有效括号组合
n=1 -> ()
n=2 -> ()(), (())
n=3 -> ((())), (()()), (())() ,()(()), ()()()
先右括号 不合法,就不用递归 --> 剪枝
记住left用了多少,right用了多少,O(2^n)
使用递归函数_gen(self,left,right,n,result),
if(left== n and right== n)self.list.append(result)
if(left<n)self._gen( left+1,right,n,result+"(" )
if(left>right and right<n)self._gen( left, right+1,n,result+")" )
10 剪枝
搜索优化手段
有空就更新一点点…