Skip to content

update #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/ISSUE_TEMPLATE/daily-problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
name: Daily Problem
about: Contribute Daily Problem
title: "【每日一题】- 2020-xx-xx - xxx "
labels: Daily Question
assignees: ''

---

[anything]

题目地址:xxxxxx
10 changes: 10 additions & 0 deletions .github/ISSUE_TEMPLATE/translation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: Translation
about: translation
title: 'feat(translation): xxxxxxx'
labels: 国际化
assignees: ''

---


2 changes: 1 addition & 1 deletion README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ The data structures mainly include:

- [Data Structure](./thinkings/basic-data-structure-en.md) (Drafts)
- [Basic Algorithm](./thinkings/basic-algorithm-en.md)(Drafts)
- [Binary Tree Traversal](./thinkings/binary-tree-traversal-en.md)
- [Binary Tree Traversal](./thinkings/binary-tree-traversal.en.md)
- [Dynamic Programming](./thinkings/dynamic-programming-en.md)
- [Huffman Encode and Run Length Encode](./thinkings/run-length-encode-and-huffman-encode-en.md)
- [Bloom Filter](./thinkings/bloom-filter-en.md)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ leetcode 题解,记录自己的 leetcode 解题之路。
- [0516.longest-palindromic-subsequence](./problems/516.longest-palindromic-subsequence.md)
- [0518.coin-change-2](./problems/518.coin-change-2.md)
- [0547.friend-circles](./problems/547.friend-circles-en.md) 🆕
- [0560.subarray-sum-equals-k](./problems/560.subarray-sum-equals-k.md)
- [0609.find-duplicate-file-in-system](./problems/609.find-duplicate-file-in-system.md)
- [0820.short-encoding-of-words](./problems/820.short-encoding-of-words.md) 🆕
- [0875.koko-eating-bananas](./problems/875.koko-eating-bananas.md)
Expand Down
26 changes: 22 additions & 4 deletions problems/102.binary-tree-level-order-traversal.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ https://leetcode.com/problems/binary-tree-level-order-traversal/description/

## 题目描述
```
Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。



示例:
二叉树:[3,9,20,null,null,15,7],

For example:
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its level order traversal as:
返回其层次遍历结果:

[
[3],
[9,20],
Expand All @@ -23,6 +27,8 @@ return its level order traversal as:

## 思路

这是一个典型的二叉树遍历问题, 关于二叉树遍历,我总结了一个[专题](https://github.com/azl397985856/leetcode/blob/master/thinkings/binary-tree-traversal.md),大家可以先去看下那个,然后再来刷这道题。

这道题可以借助`队列`实现,首先把root入队,然后入队一个特殊元素Null(来表示每层的结束)。


Expand Down Expand Up @@ -223,6 +229,18 @@ class Solution:
return result
```

***复杂度分析***
- 时间复杂度:$O(N)$,其中N为树中节点总数。
- 空间复杂度:$O(N)$,其中N为树中节点总数。

更多题解可以访问我的LeetCode题解仓库:https://github.com/azl397985856/leetcode 。 目前已经30K star啦。

大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解

## 扩展

实际上这道题方法很多, 比如经典的三色标记法。

## 相关题目
- [103.binary-tree-zigzag-level-order-traversal](./103.binary-tree-zigzag-level-order-traversal.md)
- [104.maximum-depth-of-binary-tree](./104.maximum-depth-of-binary-tree.md)
53 changes: 42 additions & 11 deletions problems/560.subarray-sum-equals-k.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## 题目地址

https://leetcode.com/problems/subarray-sum-equals-k/description/

## 题目描述

```
Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k.
Expand All @@ -13,31 +15,60 @@ The length of the array is in range [1, 20,000].
The range of numbers in the array is [-1000, 1000] and the range of the integer k is [-1e7, 1e7].
```

## 思路
符合直觉的做法是暴力求解所有的子数组,然后分别计算和,如果等于k,count就+1.这种做法的时间复杂度为O(n^2).

这里有一种更加巧妙的方法,我们可以借助额外的空间,使用hashmap来简化时间复杂度,这种算法的时间复杂度可以达到O(n).
符合直觉的做法是暴力求解所有的子数组,然后分别计算和,如果等于 k,count 就+1.这种做法的时间复杂度为 O(n^2),代码如下:

```python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
for i in range(n):
for j in range(i, n):
if (sum(nums[i:j + 1]) == k): cnt += 1
return cnt
```

实际上刚开始看到这题目的时候,我想“是否可以用滑动窗口解决?”。但是很快我就放弃了,因为看了下数组中项的取值范围有负数,这样我们扩张或者收缩窗口就比较复杂。第二个想法是前缀和,保存一个数组的前缀和,然后利用差分法得出任意区间段的和,这种想法是可行的,代码如下:

```python
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
cnt, n = 0, len(nums)
pre = [0] * (n + 1)
for i in range(1, n + 1):
pre[i] = pre[i - 1] + nums[i - 1]
for i in range(1, n + 1):
for j in range(i, n + 1):
if (pre[j] - pre[i - 1] == k): cnt += 1
return cnt
```

这里有一种更加巧妙的方法,可以不使用前缀和数组,而是使用 hashmap 来简化时间复杂度,这种算法的时间复杂度可以达到 O(n).

具体算法:

我们维护一个hashmap,hashmap的key为累加值acc,value为累加值acc出现的次数。
我们迭代数组,然后不断更新acc和hashmap,如果acc 等于k,那么很明显应该+1. 如果hashmap[acc - k] 存在,
我们就把它加到结果中去即可。
- 维护一个 hashmap,hashmap 的 key 为累加值 acc,value 为累加值 acc 出现的次数。
- 迭代数组,然后不断更新 acc 和 hashmap,如果 acc 等于 k,那么很明显应该+1. 如果 hashmap[acc - k] 存在,我们就把它加到结果中去即可。

语言比较难以解释,我画了一个图来演示nums = [1,2,3,3,0,3,4,2], k = 6的情况
语言比较难以解释,我画了一个图来演示 nums = [1,2,3,3,0,3,4,2], k = 6 的情况

![560.subarray-sum-equals-k](../assets/problems/560.subarray-sum-equals-k.jpg)

如图,当访问到nums[3]的时候,hashmap如图所示,这个时候count为2.
如图,当访问到 nums[3]的时候,hashmap 如图所示,这个时候 count 为 2.
其中之一是[1,2,3],这个好理解。还有一个是[3,3].

这个[3,3]正是我们通过hashmap[acc - k]即hashmap[9 - 6]得到的。
这个[3,3]正是我们通过 hashmap[acc - k]即 hashmap[9 - 6]得到的。

## 关键点解析

- 可以利用hashmap记录和的累加值来避免重复计算
- 前缀和
- 可以利用 hashmap 记录和的累加值来避免重复计算

## 代码

* 语言支持:JS, Python
- 语言支持:JS, Python

Javascript Code:

Expand All @@ -52,7 +83,7 @@ Javascript Code:
* @param {number} k
* @return {number}
*/
var subarraySum = function(nums, k) {
var subarraySum = function (nums, k) {
const hashmap = {};
let acc = 0;
let count = 0;
Expand Down
193 changes: 193 additions & 0 deletions thinkings/binary-tree-traversal.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Binary Tree Traversal

## Overview

Binary tree as a basic data structure and traversal as a fundamental algorithm, their combination leads to a lot of classic problems. This patern is often seen in many problems, either directly or indirectly.

> If you have grasped the traversal of binary trees, other complicated trees will probably be easy for you.

Following are the generally used ways for traversing trees.

- Depth First Traversals (DFS): Inorder, Preorder, Postorder

- Breadth First or Level Order Traversal (BFS)

There are applications for both DFS and BFS. Check out leetcode problem No.301 and No.609.

Stack can be used to simplify the process of DFS traversal. Besides, since tree is a recursive data structure, recursion and stack are two key points for DFS.

Graph for DFS:

![binary-tree-traversal-dfs](../assets/thinkings/binary-tree-traversal-dfs.gif)

(from https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/depth-first-search)

The key point of BFS is how to decide whether the traversal of each level is done. The answer is using a variable as a flag to represent the end of the traversal of current level.

Let's dive into details.

## Preorder Traversal

related problem[144.binary-tree-preorder-traversal](../problems/144.binary-tree-preorder-traversal.md)

The traversal order of preorder traversal is `root-left-right`.

Algorithm Preorder

1. Visit the root node and push it into a stack.

2. Pop a node from the stack, and push its right and left child node into the stack respectively.

3. Repeat step 2.

Conclusion: This problem involves the clasic recursive data structure (i.e. a binary tree), and the algorithm above demonstrates how a simplified solution can be reached by using a stack.

If you look at the bigger picture, you'll find that the process of traversal is as followed. `Visit the left subtrees repectively from top to bottom, and visit the right subtrees repectively from bottom to top`. If we are to implement it from this perspective, things will be somewhat different. For the `top to bottom` part we can simply use recursion, and for the `bottom to top` part we can turn to stack.

The traversal will look something like this.

![binary-tree-traversal-preorder](../assets/thinkings/binary-tree-traversal-preorder.png)

This way of problem solving is a bit similar to `backtrack`, on which I have written a post. You can benefit a lot from it because it can be used to `solve all three DFS traversal problems` mentioned aboved. If you don't know this yet, make a memo on it.

## Inorder Traversal

related problem[94.binary-tree-inorder-traversal](../problems/94.binary-tree-inorder-traversal.md)

The traversal order of inorder traversal is `left-root-right`.

So the root node is not printed first. Things are getting a bit complicated here.

Algorithm Inorder

1. Visit the root and push it into a stack.

2. If there is a left child node, push it into the stack. Repeat this process until a leaf node reached.

> At this point the root node and all the left nodes are in the stack.

3. Start popping nodes from the stack. If a node has a right child node, push the child node into the stack. Repeat step 2.

It's worth pointing out that the inorder traversal of a binary search tree (BST) is a sorted array, which is helpful for coming up simplified solutions for some problems. e.g. [230.kth-smallest-element-in-a-bst](../problems/230.kth-smallest-element-in-a-bst.md) and [98.validate-binary-search-tree](../problems/98.validate-binary-search-tree.md)

## Postorder Traversal

related problem[145.binary-tree-postorder-traversal](../problems/145.binary-tree-postorder-traversal.md)

The traversal order of postorder traversal is `left-right-root`.

This one is a bit of a challange. It deserves the `hard` tag of leetcode.

In this case, the root node is printed not as the first but the last one. A cunning way to do it is to:

Record whether the current node has been visited. If 1) it's a leaf node or 2) both its left and right subtrees have been traversed, then it can be popped from the stack.

As for `1) it's a leaf node`, you can easily tell whether a node is a leaf if both its left and right are `null`.

As for `2) both its left and right subtrees have been traversed`, we only need a variable to record whether a node has been visited or not. In the worst case, we need to record the status for every single node and the space complexity is O(n). But if you come to think about it, as we are using a stack and start printing the result from the leaf nodes, it makes sense that we only record the status for the current node popping from the stack, reducing the space complexity to O(1). Please click the link above for more details.

## Level Order Traversal

The key point of level order traversal is how do we know whether the traversal of each level is done. The answer is that we use a variable as a flag representing the end of the traversal of the current level.

![binary-tree-traversal-bfs](../assets/thinkings/binary-tree-traversal-bfs.gif)

(from https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/tree/breadth-first-search)

Algorithm Level Order

1. Visit the root node, put it in a FIFO queue, put in the queue a special flag (we are using `null` here).

2. Dequeue a node.

3. If the node equals `null`, it means that all nodes of the current level have been visited. If the queue is empty, we do nothing. Or else we put in another `null`.

4. If the node is not `null`, meaning the traversal of current level has not finished yet, we enqueue its left subtree and right subtree repectively.

related problem[102.binary-tree-level-order-traversal](../problems/102.binary-tree-level-order-traversal.md)

## Bi-color marking

We know that there is a tri-color marking in garbage collection algorithm, which works as described below.

- The white color represents "not visited".

- The gray color represents "not all child nodes visited".

- The black color represents "all child nodes visited".

Enlightened by tri-color marking, a bi-color marking method can be invented to solve all three traversal problems with one solution.

The core idea is as followed.

- Use a color to mark whether a node has been visited or not. Nodes yet to be visited are marked as white and visited nodes are marked as gray.

- If we are visiting a white node, turn it into gray, and push it's right child node, itself, and it's left child node into the stack respectively.

- If we are visiting a gray node, print it.

Implementing inorder traversal with tri-color marking:

```python
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
WHITE, GRAY = 0, 1
res = []
stack = [(WHITE, root)]
while stack:
color, node = stack.pop()
if node is None: continue
if color == WHITE:
stack.append((WHITE, node.right))
stack.append((GRAY, node))
stack.append((WHITE, node.left))
else:
res.append(node.val)
return res
```

Implementation of preorder and postorder traversal algorithms can be easily done by changing the order of pushing the child nodes into the stack.

## Morris Traversal

We can also use a method called Morris traversal, which involves no recursion or stack, and the time complexity is O(1).

```python
def MorrisTraversal(root):
curr = root

while curr:
# If left child is null, print the
# current node data. And, update
# the current pointer to right child.
if curr.left is None:
print(curr.data, end= " ")
curr = curr.right

else:
# Find the inorder predecessor
prev = curr.left

while prev.right is not None and prev.right is not curr:
prev = prev.right

# If the right child of inorder
# predecessor already points to
# the current node, update the
# current with it's right child
if prev.right is curr:
prev.right = None
curr = curr.right

# else If right child doesn't point
# to the current node, then print this
# node's data and update the right child
# pointer with the current node and update
# the current with it's left child
else:
print (curr.data, end=" ")
prev.right = curr
curr = curr.left
```

Reference: [what-is-morris-traversal](https://www.educative.io/edpresso/what-is-morris-traversal)