记录在《极客时间》平台上《算法面试通关40讲》专栏的学习笔记
作者信息:覃超 前Facebook工程师,卡内基梅隆大学计算机硕士
课程海报如下:欢迎扫描右下方二维码订阅(有优惠哦)
- 第1讲 合格程序员第一步:算法与数据结构
- 第2讲 如何事半功倍的学习算法与数据结构
- 第3讲 如何计算算法复杂度
- 第4讲 如何通过LeetCode来进行算法题目练习
- 第5讲 理论讲解:数组&链表
- 第6讲 面试题:翻转一个单链表&判断链表是否有环
- 第7讲 理论讲解:堆栈&队列
- 第8讲 面试题:判断括号字符串是否有效
- 第9讲 面试题:用队列实现栈&用栈实现队列
- 第10讲 理论讲解:优先队列
- 第11讲 面试题:返回数据流中的第K大元素
- 第12讲 面试题:返回滑动窗口中的最大值
- 第13讲 理论讲解:哈希表
- 第14讲 面试题:有效的字母异位词
- 第15讲 面试题:两数之和
- 第16讲 面试题:三数之和
- 第17讲 理论讲解:树&二叉树&二叉搜索树
- 第18讲 面试题:验证二叉搜索树
- 第19讲 面试题:二叉树&二叉搜索树的最近公共祖先
- 第20讲 理论讲解:二叉树遍历
- 第21讲 理论讲解:递归&分治
- 第22讲 面试题:Pow(x,n)
- 第23讲 面试题:求众数
- 第24讲 理论讲解:贪心算法
- 第25讲 面试题:买卖股票的最佳时机
- 第26讲 理论讲解:广度优先搜索
- 第27讲 理论讲解:深度优先搜索
- 第28讲 面试题:二叉树层次遍历
- 第29讲 面试题:二叉树的最大和最小深度
- 第30讲 面试题:生成有效括号组合
- 第31讲 理论讲解:剪枝
- 第32讲 面试题:N皇后问题
- 第33讲 面试题:数独问题
- 第34讲 理论讲解:二分查找
- 第35讲 面试题:实现一个求解平方根的函数
- 第36讲 理论讲解:字典树
- 第37讲 面试题:实现一个字典树
- 第38讲 面试题:二维网格中的单词搜索问题
- 第39讲 理论讲解:位运算
- 第40讲 最后一些经验分享
- 第41讲 面试题:2的幂次方问题&比特位计数问题
- 第42讲 面试题:N皇后问题的另一种解法
- 第43讲 理论理解:动态规划(上)
- 第44讲 理论理解:动态规划(下)
- 第45讲 面试题:爬楼梯
- 第46讲 面试题:三角形的最小路径和
- 第47讲 面试题:乘积最大子序列
- 第48讲 面试题:股票买卖系列
- 第49讲 面试题:最长上升子序列
- 第50讲 面试题:零钱兑换
- 第51讲 面试题:编辑距离
- 第52讲 理论讲解:并查集
- 第53讲 面试题:岛屿的个数&朋友圈(上)
- 第54讲 面试题:岛屿的个数&朋友圈(下)
- 第55讲 理论讲解:LRU Cache
- 第56讲 面试题:设计和实现一个LRU Cache缓存机制
- 第57讲 理论讲解:布隆过滤器
- 第58讲 课程重点回顾
- 第59讲 FAQ答疑&面试中切题四件套
- 第60讲 回到起点:斐波拉契数列
- 第61讲 白板实战番外篇:斐波拉契数列
- 第62讲 最后一些经验分享
- 完结
每天的工作计划使用优先级队列的方式进行计划;
加密货币中有大量的算法和数据结构,区块使用LinkedList,区块内使用二叉树来表示交易信息;
编程是内功的修炼;
算法与数据结构有趣且实用;
推荐书籍:《异类不一样的成功启示录》切碎知识点;刻意练习;获得反馈
明确题目意识;找到所有可能的解法;编码实现;进行测试;
时间复杂度:常数,对数,线性,平方,立方,指数,阶乘
斐波那契数列的时间复杂度为O(2^n)
Master Theorem 二分查找O(logn),二叉树遍历O(n),二维矩阵查找O(n)
坚持刻意练习;练习缺陷弱点地方;不舒服,不爽和枯燥;
数组:内存中连续的存储区域,根据索引O(1)查找,插入和删除操作O(n)
链表:很多的插入和删除操作,不知道有多少个元素,查找是O(n),单链表和双链表
题目列表:
206翻转一个链表
def reverseList(self, head): cur, prev = head, None while cur: # 一次性赋值,先计算好右边一次性赋值 cur.next, prev, cur = prev, cur, cur.next return prev
24 对一个链表进行两两翻转
def swapPairs(self, head) pre, pre.next = self, head while pre.next and pre.next.next: a = pre.next b = a.next pre.next, b.next, a.next = b, a, b.next pre = a return self.next
141 判断链表是否有环
def hasCycle(self, head) fast = slow = head while slow and fast and fast.next: slow = slow.next fast = fast.next.next if slow if fast: return Ture return False
栈:先进后出;查找O(n),插入删除O(1)
队列:先进先出;查找O(n),插入删除O(1)
20 判断括号是否合法 栈空O(n)
def isValid(self, s): stach = [] paren_map = {')':'(',']':'[','}':'{'} for c in s: if c not in paren_map: stack.append(c) //新进入一个符号栈是空的或者与栈顶元素不匹配则false elif not stack or paren_map[c] != stack.pop(): return False return not stack public boolean isValid(String s){ int length; do{ length = s.length(); s = s.replace("()", "").replace("[]","").replace("{}",""); }while(length != s.length()); return s.length() == 0; }
1.左括号直接压进栈;
2.右括号如果栈不为空与栈顶匹配则出栈;
3.栈空则说明合法
栈实现队列:使用两个栈,输入栈进行数据输入,输出栈输出数据;
进行输出时判断输出栈是否有元素,如果没有就将输入栈全部放入输出栈;
队列实现栈:两个队列,一个入队列一个临时队列,在进行数据输出时将除了队列底部最后一个数据留下外, 其他数据放入临时队列,数据读取完之后再将临时队列的元素放入原队列。
优先队列的实现方式:
- heap(二叉树,多项式堆,斐波那契堆)
- 二叉搜索树 二叉堆(最小最大的数据在最上面)合并O(n),删除O(logn)
703 实时返回第K大元素 1.每次保存K个最大值,每次进行更新,需要排序O(nklogn)
2.维护一个size为k的小顶堆 O(nlogk)
class KthLargest{ final PriorityQueue<Integer> q; final int k ; public KthLargest(int k , int[] a){ this.k =k; q = new PriorityQueue<>(k); for(int n : a){ add(n); } public int add(int k ){ if(q.size() < k){ q.offer(n); }else if(q.peek < n){ q.poll(); q.offer(n); } return q.peek; } }
239 滑动窗口最大值
- 维护一个大顶堆,每次最大值就是堆顶元素;O(nlogk)
- 双端队列, k个元素进入队列,进入队列时如果前面数都比该数小就出队列,保证区间 最左端是最大值;
def maxSlidingWindow1(self, nums, k){ if not nums : return [] window, res = [],[] for i, x in enumerate(nums): if i>= k and window[0] <= i-k: window.pop(0) while window and nums[window[-1]] <= x: window.pop() window.append(i) if(i >= k-1): res.append(nums[window[0]]) return res }
1.哈希表,哈希函数,哈希冲突(拉链法)
2.map表示映射关系【HashMap vs TreeMap】
3.set里面的元素不重复<哈希表或者二叉树>HashSet(O(1)) vs TreeSet(二叉树(O(logn)))
4.list可以重复
242 有效的字母异位词 “rat” "art"
- sort 然后进行比较 O(nlogn)
- map 计数{letter:count} O(n)
def isAnagram3(self, s, t) return sorted(s) == sorted(t) def isAnagram2(self, s, t) dic1, dic2 = {}, {} for item in s : dic1[item] = dic1.get(item, 0) +1; for item in s : dic2[item] = dic2.get(item, 0) +1; return dic1 == dic2 def isAnagram1(self, s, t) dic1, dic2 = [0]*26, [0]*26 for item in s : dic1[ord(item) - ord('a')] += 1 for item in s : dic2[ord(item) - ord('a')] += 1 return dic1 == dic2
1.暴力搜索 O(n^2)
2.构建hashmap,可以一次性先放进去也可以走着放着。
15 三数和 18. 四数和
- 暴力循环 O(n^3)
- SET集合,枚举a,b,在set里面查找(-a-b)
- 先排序再找,枚举a,在剩下的数组中维护两个指针(left,right), 不断从两边向中间夹, sum(a,b,c)>0 right--;sum(a,b,c)<0 left++;
def thressSum(self, nums): if len(nums) < 3: return [] nums.sort() res = set() for i,v in enumerate(nums[:2]): if i>=1 and v == nums[i-1]: continue d = {} for x in nums[i+1:]: if x not in d: d[-v-x] = 1 else: res.add((v, -v-x, x)) return map(list, res)
1.树:特殊的链表
2. 二叉树:只有两个孩子
3. 二叉搜索树:左子树<根,根> 右子树 O(logn)
98 验证二叉搜索树
- 中序遍历 左子树 根 右子树,是升序的 O(n)
- 递归方式,max<-node.left;min<-node.right;max<root;min>root; O(N)
def isValidBST(self, root): inorder = self.inorder(root) return inorder == list(sorted(set(inorder))) def inorder (self, root): if root is none: return [] return self.inorder(root.left) + [root.val] + self.inorder(root.right) def isValidBST(self, root): self.prev = None return self.helper(root) def helper(self,root): if root is None: return True if not self.helper(root.left): return False if self.prev and self.prev.val >= root.val return False self.prev = root return self.helper(root.right) public boolean isValid(TreeNode root, Integer min , Integer max){ if(root == null) return ture; if(min != null && root.val <= min ) return false; if(max != null && root.val >- max) return false; return isValid(root.left,min,root.val) && isValid(root.right,root.val,max);