mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-08 00:43:04 +08:00
Merge branch 'youngyangyang04:master' into master
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
👉 推荐 [在线阅读](http://programmercarl.com/) (Github在国内访问经常不稳定)
|
||||
👉 推荐 [在线阅读](http://programmercarl.com/) (Github在国内访问经常不稳定)
|
||||
👉 推荐 [Gitee同步](https://gitee.com/programmercarl/leetcode-master)
|
||||
|
||||
> 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者)
|
||||
@ -494,6 +494,7 @@
|
||||
## 图论
|
||||
* [463.岛屿的周长](./problems/0463.岛屿的周长.md) (模拟)
|
||||
* [841.钥匙和房间](./problems/0841.钥匙和房间.md) 【有向图】dfs,bfs都可以
|
||||
* [127.单词接龙](./problems/0127.单词接龙.md) 广搜
|
||||
|
||||
## 并查集
|
||||
* [684.冗余连接](./problems/0684.冗余连接.md) 【并查集基础题目】
|
||||
|
@ -206,6 +206,23 @@ function twoSum(array $nums, int $target): array
|
||||
}
|
||||
```
|
||||
|
||||
Swift:
|
||||
```swift
|
||||
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
|
||||
var res = [Int]()
|
||||
var dict = [Int : Int]()
|
||||
for i in 0 ..< nums.count {
|
||||
let other = target - nums[i]
|
||||
if dict.keys.contains(other) {
|
||||
res.append(i)
|
||||
res.append(dict[other]!)
|
||||
return res
|
||||
}
|
||||
dict[nums[i]] = i
|
||||
}
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -214,19 +214,17 @@ end
|
||||
```
|
||||
Rust:
|
||||
```rust
|
||||
pub fn remove_element(nums: &mut Vec<i32>, val: i32) -> &mut Vec<i32> {
|
||||
let mut start: usize = 0;
|
||||
while start < nums.len() {
|
||||
if nums[start] == val {
|
||||
nums.remove(start);
|
||||
impl Solution {
|
||||
pub fn remove_element(nums: &mut Vec<i32>, val: i32) -> i32 {
|
||||
let mut slowIdx = 0;
|
||||
for pos in (0..nums.len()) {
|
||||
if nums[pos]!=val {
|
||||
nums[slowIdx] = nums[pos];
|
||||
slowIdx += 1;
|
||||
}
|
||||
}
|
||||
start += 1;
|
||||
return (slowIdx) as i32;
|
||||
}
|
||||
nums
|
||||
}
|
||||
fn main() {
|
||||
let mut nums = vec![5,1,3,5,2,3,4,1];
|
||||
println!("{:?}",remove_element(&mut nums, 5));
|
||||
}
|
||||
```
|
||||
|
||||
@ -248,6 +246,22 @@ func removeElement(_ nums: inout [Int], _ val: Int) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
int removeElement(int* nums, int numsSize, int val){
|
||||
int slow = 0;
|
||||
for(int fast = 0; fast < numsSize; fast++) {
|
||||
//若快指针位置的元素不等于要删除的元素
|
||||
if(nums[fast] != val) {
|
||||
//将其挪到慢指针指向的位置,慢指针+1
|
||||
nums[slow++] = nums[fast];
|
||||
}
|
||||
}
|
||||
//最后慢指针的大小就是新的数组的大小
|
||||
return slow;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||
|
@ -183,6 +183,32 @@ class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
```java
|
||||
// 解法2:通过判断path中是否存在数字,排除已经选择的数字
|
||||
class Solution {
|
||||
List<List<Integer>> result = new ArrayList<>();
|
||||
LinkedList<Integer> path = new LinkedList<>();
|
||||
public List<List<Integer>> permute(int[] nums) {
|
||||
if (nums.length == 0) return result;
|
||||
backtrack(nums, path);
|
||||
return result;
|
||||
}
|
||||
public void backtrack(int[] nums, LinkedList<Integer> path) {
|
||||
if (path.size() == nums.length) {
|
||||
result.add(new ArrayList<>(path));
|
||||
}
|
||||
for (int i =0; i < nums.length; i++) {
|
||||
// 如果path中已有,则跳过
|
||||
if (path.contains(nums[i])) {
|
||||
continue;
|
||||
}
|
||||
path.add(nums[i]);
|
||||
backtrack(nums, path);
|
||||
path.removeLast();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
```python3
|
||||
|
@ -341,7 +341,7 @@ class Solution {
|
||||
|
||||
public boolean isValid(int row, int col, int n, char[][] chessboard) {
|
||||
// 检查列
|
||||
for (int i=0; i<n; ++i) {
|
||||
for (int i=0; i<row; ++i) { // 相当于剪枝
|
||||
if (chessboard[i][col] == 'Q') {
|
||||
return false;
|
||||
}
|
||||
|
@ -157,6 +157,28 @@ class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
```java
|
||||
// 版本2
|
||||
class Solution {
|
||||
public int[][] merge(int[][] intervals) {
|
||||
LinkedList<int[]> res = new LinkedList<>();
|
||||
Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
|
||||
res.add(intervals[0]);
|
||||
for (int i = 1; i < intervals.length; i++) {
|
||||
if (intervals[i][0] <= res.getLast()[1]) {
|
||||
int start = res.getLast()[0];
|
||||
int end = Math.max(intervals[i][1], res.getLast()[1]);
|
||||
res.removeLast();
|
||||
res.add(new int[]{start, end});
|
||||
}
|
||||
else {
|
||||
res.add(intervals[i]);
|
||||
}
|
||||
}
|
||||
return res.toArray(new int[res.size()][]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
```python
|
||||
|
@ -373,6 +373,58 @@ func generateMatrix(_ n: Int) -> [[Int]] {
|
||||
}
|
||||
```
|
||||
|
||||
Rust:
|
||||
|
||||
```rust
|
||||
impl Solution {
|
||||
pub fn generate_matrix(n: i32) -> Vec<Vec<i32>> {
|
||||
let mut res = vec![vec![0; n as usize]; n as usize];
|
||||
let (mut startX, mut startY, mut offset): (usize, usize, usize) = (0, 0, 1);
|
||||
let mut loopIdx = n/2;
|
||||
let mid: usize = loopIdx as usize;
|
||||
let mut count = 1;
|
||||
let (mut i, mut j): (usize, usize) = (0, 0);
|
||||
while loopIdx > 0 {
|
||||
i = startX;
|
||||
j = startY;
|
||||
|
||||
while j < (startY + (n as usize) - offset) {
|
||||
res[i][j] = count;
|
||||
count += 1;
|
||||
j += 1;
|
||||
}
|
||||
|
||||
while i < (startX + (n as usize) - offset) {
|
||||
res[i][j] = count;
|
||||
count += 1;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
while j > startY {
|
||||
res[i][j] = count;
|
||||
count += 1;
|
||||
j -= 1;
|
||||
}
|
||||
|
||||
while i > startX {
|
||||
res[i][j] = count;
|
||||
count += 1;
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
startX += 1;
|
||||
startY += 1;
|
||||
offset += 2;
|
||||
loopIdx -= 1;
|
||||
}
|
||||
|
||||
if(n % 2 == 1) {
|
||||
res[mid][mid] = count;
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
-----------------------
|
||||
|
@ -435,6 +435,59 @@ func backtrack(n,k,start int,track []int){
|
||||
}
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
int* path;
|
||||
int pathTop;
|
||||
int** ans;
|
||||
int ansTop;
|
||||
|
||||
void backtracking(int n, int k,int startIndex) {
|
||||
//当path中元素个数为k个时,我们需要将path数组放入ans二维数组中
|
||||
if(pathTop == k) {
|
||||
//path数组为我们动态申请,若直接将其地址放入二维数组,path数组中的值会随着我们回溯而逐渐变化
|
||||
//因此创建新的数组存储path中的值
|
||||
int* temp = (int*)malloc(sizeof(int) * k);
|
||||
int i;
|
||||
for(i = 0; i < k; i++) {
|
||||
temp[i] = path[i];
|
||||
}
|
||||
ans[ansTop++] = temp;
|
||||
return ;
|
||||
}
|
||||
|
||||
int j;
|
||||
for(j = startIndex; j <=n ;j++) {
|
||||
//将当前结点放入path数组
|
||||
path[pathTop++] = j;
|
||||
//进行递归
|
||||
backtracking(n, k, j + 1);
|
||||
//进行回溯,将数组最上层结点弹出
|
||||
pathTop--;
|
||||
}
|
||||
}
|
||||
|
||||
int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
|
||||
//path数组存储符合条件的结果
|
||||
path = (int*)malloc(sizeof(int) * k);
|
||||
//ans二维数组存储符合条件的结果数组的集合。(数组足够大,避免极端情况)
|
||||
ans = (int**)malloc(sizeof(int*) * 10000);
|
||||
pathTop = ansTop = 0;
|
||||
|
||||
//回溯算法
|
||||
backtracking(n, k, 1);
|
||||
//最后的返回大小为ans数组大小
|
||||
*returnSize = ansTop;
|
||||
//returnColumnSizes数组存储ans二维数组对应下标中一维数组的长度(都为k)
|
||||
*returnColumnSizes = (int*)malloc(sizeof(int) *(*returnSize));
|
||||
int i;
|
||||
for(i = 0; i < *returnSize; i++) {
|
||||
(*returnColumnSizes)[i] = k;
|
||||
}
|
||||
//返回ans二维数组
|
||||
return ans;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -311,6 +311,35 @@ class solution {
|
||||
}
|
||||
```
|
||||
|
||||
### 559.n叉树的最大深度
|
||||
```java
|
||||
class solution {
|
||||
/**
|
||||
* 迭代法,使用层序遍历
|
||||
*/
|
||||
public int maxDepth(Node root) {
|
||||
if (root == null) return 0;
|
||||
int depth = 0;
|
||||
Queue<Node> que = new LinkedList<>();
|
||||
que.offer(root);
|
||||
while (!que.isEmpty())
|
||||
{
|
||||
depth ++;
|
||||
int len = que.size();
|
||||
while (len > 0)
|
||||
{
|
||||
Node node = que.poll();
|
||||
for (int i = 0; i < node.children.size(); i++)
|
||||
if (node.children.get(i) != null)
|
||||
que.offer(node.children.get(i));
|
||||
len--;
|
||||
}
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## python
|
||||
|
||||
### 104.二叉树的最大深度
|
||||
@ -505,21 +534,51 @@ var maxdepth = function(root) {
|
||||
|
||||
二叉树最大深度层级遍历
|
||||
```javascript
|
||||
var maxdepth = function(root) {
|
||||
//使用递归的方法 递归三部曲
|
||||
//1. 确定递归函数的参数和返回值
|
||||
const getdepth=function(node){
|
||||
//2. 确定终止条件
|
||||
if(node===null){
|
||||
return 0;
|
||||
var maxDepth = function(root) {
|
||||
if(!root) return 0
|
||||
let count = 0
|
||||
const queue = [root]
|
||||
while(queue.length) {
|
||||
let size = queue.length
|
||||
/* 层数+1 */
|
||||
count++
|
||||
while(size--) {
|
||||
let node = queue.shift();
|
||||
node.left && queue.push(node.left);
|
||||
node.right && queue.push(node.right);
|
||||
}
|
||||
//3. 确定单层逻辑
|
||||
let leftdepth=getdepth(node.left);
|
||||
let rightdepth=getdepth(node.right);
|
||||
let depth=1+math.max(leftdepth,rightdepth);
|
||||
return depth;
|
||||
}
|
||||
return getDepth(root);
|
||||
return count
|
||||
};
|
||||
```
|
||||
|
||||
N叉树的最大深度 递归写法
|
||||
```js
|
||||
var maxDepth = function(root) {
|
||||
if(!root) return 0
|
||||
let depth = 0
|
||||
for(let node of root.children) {
|
||||
depth = Math.max(depth, maxDepth(node))
|
||||
}
|
||||
return depth + 1
|
||||
}
|
||||
```
|
||||
|
||||
N叉树的最大深度 层序遍历
|
||||
```js
|
||||
var maxDepth = function(root) {
|
||||
if(!root) return 0
|
||||
let count = 0
|
||||
let queue = [root]
|
||||
while(queue.length) {
|
||||
let size = queue.length
|
||||
count++
|
||||
while(size--) {
|
||||
let node = queue.shift()
|
||||
node && (queue = [...queue, ...node.children])
|
||||
}
|
||||
}
|
||||
return count
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -221,6 +221,30 @@ class SolutionDP2:
|
||||
```
|
||||
|
||||
Go:
|
||||
```go
|
||||
func numDistinct(s string, t string) int {
|
||||
dp:= make([][]int,len(s)+1)
|
||||
for i:=0;i<len(dp);i++{
|
||||
dp[i] = make([]int,len(t)+1)
|
||||
}
|
||||
// 初始化
|
||||
for i:=0;i<len(dp);i++{
|
||||
dp[i][0] = 1
|
||||
}
|
||||
// dp[0][j] 为 0,默认值,因此不需要初始化
|
||||
for i:=1;i<len(dp);i++{
|
||||
for j:=1;j<len(dp[i]);j++{
|
||||
if s[i-1] == t[j-1]{
|
||||
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
|
||||
}else{
|
||||
dp[i][j] = dp[i-1][j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[len(dp)-1][len(dp[0])-1]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Javascript:
|
||||
```javascript
|
||||
|
@ -139,17 +139,11 @@ Java:
|
||||
// 贪心思路
|
||||
class Solution {
|
||||
public int maxProfit(int[] prices) {
|
||||
int sum = 0;
|
||||
int profit = 0;
|
||||
int buy = prices[0];
|
||||
int result = 0;
|
||||
for (int i = 1; i < prices.length; i++) {
|
||||
profit = prices[i] - buy;
|
||||
if (profit > 0) {
|
||||
sum += profit;
|
||||
}
|
||||
buy = prices[i];
|
||||
result += Math.max(prices[i] - prices[i - 1], 0);
|
||||
}
|
||||
return sum;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -235,6 +229,21 @@ var maxProfit = function(prices) {
|
||||
};
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
int maxProfit(int* prices, int pricesSize){
|
||||
int result = 0;
|
||||
int i;
|
||||
//从第二个元素开始遍历数组,与之前的元素进行比较
|
||||
for(i = 1; i < pricesSize; ++i) {
|
||||
//若该元素比前面元素大,则说明有利润。代表买入
|
||||
if(prices[i] > prices[i-1])
|
||||
result+= prices[i]-prices[i-1];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||
|
150
problems/0127.单词接龙.md
Normal file
150
problems/0127.单词接龙.md
Normal file
@ -0,0 +1,150 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ"><img src="https://img.shields.io/badge/PDF下载-代码随想录-blueviolet" alt=""></a>
|
||||
<a href="https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw"><img src="https://img.shields.io/badge/刷题-微信群-green" alt=""></a>
|
||||
<a href="https://space.bilibili.com/525438321"><img src="https://img.shields.io/badge/B站-代码随想录-orange" alt=""></a>
|
||||
<a href="https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ"><img src="https://img.shields.io/badge/知识星球-代码随想录-blue" alt=""></a>
|
||||
</p>
|
||||
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||||
|
||||
# 127. 单词接龙
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/word-ladder/)
|
||||
|
||||
|
||||
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
|
||||
* 序列中第一个单词是 beginWord 。
|
||||
* 序列中最后一个单词是 endWord 。
|
||||
* 每次转换只能改变一个字母。
|
||||
* 转换过程中的中间单词必须是字典 wordList 中的单词。
|
||||
* 给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
|
||||
|
||||
|
||||
示例 1:
|
||||
|
||||
* 输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
|
||||
* 输出:5
|
||||
* 解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
|
||||
|
||||
示例 2:
|
||||
* 输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
|
||||
* 输出:0
|
||||
* 解释:endWord "cog" 不在字典中,所以无法进行转换。
|
||||
|
||||
|
||||
# 思路
|
||||
|
||||
以示例1为例,从这个图中可以看出 hit 到 cog的路线,不止一条,有三条,两条是最短的长度为5,一条长度为6。
|
||||
|
||||

|
||||
|
||||
本题只需要求出最短长度就可以了,不用找出路径。
|
||||
|
||||
所以这道题要解决两个问题:
|
||||
|
||||
* 图中的线是如何连在一起的
|
||||
* 起点和终点的最短路径长度
|
||||
|
||||
|
||||
首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接。
|
||||
|
||||
然后就是求起点和终点的最短路径长度,**这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径**。因为广搜就是以起点中心向四周扩散的搜索。
|
||||
|
||||
本题如果用深搜,会非常麻烦。
|
||||
|
||||
另外需要有一个注意点:
|
||||
|
||||
* 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
|
||||
* 本题给出集合是数组型的,可以转成set结构,查找更快一些
|
||||
|
||||
C++代码如下:(详细注释)
|
||||
|
||||
```CPP
|
||||
class Solution {
|
||||
public:
|
||||
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
|
||||
// 将vector转成unordered_set,提高查询速度
|
||||
unordered_set<string> wordSet(wordList.begin(), wordList.end());
|
||||
// 如果endWord没有在wordSet出现,直接返回0
|
||||
if (wordSet.find(endWord) == wordSet.end()) return 0;
|
||||
// 记录word是否访问过
|
||||
unordered_map<string, int> visitMap; // <word, 查询到这个word路径长度>
|
||||
// 初始化队列
|
||||
queue<string> que;
|
||||
que.push(beginWord);
|
||||
// 初始化visitMap
|
||||
visitMap.insert(pair<string, int>(beginWord, 1));
|
||||
|
||||
while(!que.empty()) {
|
||||
string word = que.front();
|
||||
que.pop();
|
||||
int path = visitMap[word]; // 这个word的路径长度
|
||||
for (int i = 0; i < word.size(); i++) {
|
||||
string newWord = word; // 用一个新单词替换word,因为每次置换一个字母
|
||||
for (int j = 0 ; j < 26; j++) {
|
||||
newWord[i] = j + 'a';
|
||||
if (newWord == endWord) return path + 1; // 找到了end,返回path+1
|
||||
// wordSet出现了newWord,并且newWord没有被访问过
|
||||
if (wordSet.find(newWord) != wordSet.end()
|
||||
&& visitMap.find(newWord) == visitMap.end()) {
|
||||
// 添加访问信息
|
||||
visitMap.insert(pair<string, int>(newWord, path + 1));
|
||||
que.push(newWord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# 其他语言版本
|
||||
|
||||
## Java
|
||||
|
||||
```java
|
||||
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
|
||||
HashSet<String> wordSet = new HashSet<>(wordList); //转换为hashset 加快速度
|
||||
if (wordSet.size() == 0 || !wordSet.contains(endWord)) { //特殊情况判断
|
||||
return 0;
|
||||
}
|
||||
Queue<String> queue = new LinkedList<>(); //bfs 队列
|
||||
queue.offer(beginWord);
|
||||
Map<String, Integer> map = new HashMap<>(); //记录单词对应路径长度
|
||||
map.put(beginWord, 1);
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
String word = queue.poll(); //取出队头单词
|
||||
int path = map.get(word); //获取到该单词的路径长度
|
||||
for (int i = 0; i < word.length(); i++) { //遍历单词的每个字符
|
||||
char[] chars = word.toCharArray(); //将单词转换为char array,方便替换
|
||||
for (char k = 'a'; k <= 'z'; k++) { //从'a' 到 'z' 遍历替换
|
||||
chars[i] = k; //替换第i个字符
|
||||
String newWord = String.valueOf(chars); //得到新的字符串
|
||||
if (newWord.equals(endWord)) { //如果新的字符串值与endWord一致,返回当前长度+1
|
||||
return path + 1;
|
||||
}
|
||||
if (wordSet.contains(newWord) && !map.containsKey(newWord)) { //如果新单词在set中,但是没有访问过
|
||||
map.put(newWord, path + 1); //记录单词对应的路径长度
|
||||
queue.offer(newWord);//加入队尾
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0; //未找到
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
## Go
|
||||
|
||||
## JavaScript
|
||||
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||
* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ)
|
||||
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码.jpg width=450> </img></div>
|
@ -200,6 +200,7 @@ public:
|
||||
|
||||
Java:
|
||||
```java
|
||||
// 解法1
|
||||
class Solution {
|
||||
public int canCompleteCircuit(int[] gas, int[] cost) {
|
||||
int sum = 0;
|
||||
@ -221,7 +222,26 @@ class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 解法2
|
||||
class Solution {
|
||||
public int canCompleteCircuit(int[] gas, int[] cost) {
|
||||
int curSum = 0;
|
||||
int totalSum = 0;
|
||||
int index = 0;
|
||||
for (int i = 0; i < gas.length; i++) {
|
||||
curSum += gas[i] - cost[i];
|
||||
totalSum += gas[i] - cost[i];
|
||||
if (curSum < 0) {
|
||||
index = (i + 1) % gas.length ;
|
||||
curSum = 0;
|
||||
}
|
||||
}
|
||||
if (totalSum < 0) return -1;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
```
|
||||
Python:
|
||||
```python
|
||||
class Solution:
|
||||
@ -283,6 +303,35 @@ var canCompleteCircuit = function(gas, cost) {
|
||||
};
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){
|
||||
int curSum = 0;
|
||||
int i;
|
||||
int min = INT_MAX;
|
||||
//遍历整个数组。计算出每站的用油差。并将其与最小累加量比较
|
||||
for(i = 0; i < gasSize; i++) {
|
||||
int diff = gas[i] - cost[i];
|
||||
curSum += diff;
|
||||
if(curSum < min)
|
||||
min = curSum;
|
||||
}
|
||||
//若汽油总数为负数,代表无法跑完一环。返回-1
|
||||
if(curSum < 0)
|
||||
return -1;
|
||||
//若min大于等于0,说明每一天加油量比用油量多。因此从0出发即可
|
||||
if(min >= 0)
|
||||
return 0;
|
||||
//若累加最小值为负,则找到一个非零元素(加油量大于出油量)出发。返回坐标
|
||||
for(i = gasSize - 1; i >= 0; i--) {
|
||||
min+=(gas[i]-cost[i]);
|
||||
if(min >= 0)
|
||||
return i;
|
||||
}
|
||||
//逻辑上不会返回这个0
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
# 150. 逆波兰表达式求值
|
||||
|
||||
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/)
|
||||
|
||||
根据 逆波兰表示法,求表达式的值。
|
||||
|
||||
@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
|
||||
|
||||
整数除法只保留整数部分。
|
||||
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
|
||||
|
||||
|
||||
|
||||
示例 1:
|
||||
* 输入: ["2", "1", "+", "3", " * "]
|
||||
@ -37,16 +37,21 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
|
||||
|
||||
示例 3:
|
||||
* 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]
|
||||
|
||||
* 输出: 22
|
||||
|
||||
* 解释:该算式转化为常见的中缀算术表达式为:
|
||||
|
||||
```
|
||||
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
|
||||
= ((10 * (6 / (12 * -11))) + 17) + 5
|
||||
= ((10 * (6 / -132)) + 17) + 5
|
||||
= ((10 * 0) + 17) + 5
|
||||
= (0 + 17) + 5
|
||||
= 17 + 5
|
||||
= 22
|
||||
|
||||
= ((10 * (6 / (12 * -11))) + 17) + 5
|
||||
= ((10 * (6 / -132)) + 17) + 5
|
||||
= ((10 * 0) + 17) + 5
|
||||
= (0 + 17) + 5
|
||||
= 17 + 5
|
||||
= 22
|
||||
```
|
||||
|
||||
|
||||
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
|
||||
|
||||
@ -62,7 +67,7 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
|
||||
|
||||
# 思路
|
||||
|
||||
在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)提到了 递归就是用栈来实现的。
|
||||
在上一篇文章中[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)提到了 递归就是用栈来实现的。
|
||||
|
||||
所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。
|
||||
|
||||
@ -70,12 +75,12 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
|
||||
|
||||
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。
|
||||
|
||||
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中的对对碰游戏是不是就非常像了。**
|
||||
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。**
|
||||
|
||||
如动画所示:
|
||||

|
||||
|
||||
相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)是差不错的,只不过本题不要相邻元素做消除了,而是做运算!
|
||||
相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)是差不错的,只不过本题不要相邻元素做消除了,而是做运算!
|
||||
|
||||
C++代码如下:
|
||||
|
||||
|
@ -467,6 +467,85 @@ function reverse(strArr, start, end) {
|
||||
}
|
||||
```
|
||||
|
||||
Swift:
|
||||
|
||||
```swift
|
||||
func reverseWords(_ s: String) -> String {
|
||||
var stringArr = removeSpace(s)
|
||||
reverseString(&stringArr, startIndex: 0, endIndex: stringArr.count - 1)
|
||||
reverseWord(&stringArr)
|
||||
return String(stringArr)
|
||||
}
|
||||
|
||||
/// 1、移除多余的空格(前后所有的空格,中间只留一个空格)
|
||||
func removeSpace(_ s: String) -> [Character] {
|
||||
let ch = Array(s)
|
||||
var left = 0
|
||||
var right = ch.count - 1
|
||||
// 忽略字符串前面的所有空格
|
||||
while ch[left] == " " {
|
||||
left += 1
|
||||
}
|
||||
// 忽略字符串后面的所有空格
|
||||
while ch[right] == " " {
|
||||
right -= 1
|
||||
}
|
||||
|
||||
// 接下来就是要处理中间的多余空格
|
||||
var lastArr = Array<Character>()
|
||||
while left <= right {
|
||||
// 准备加到新字符串当中的字符
|
||||
let char = ch[left]
|
||||
// 新的字符串的最后一个字符;或者原字符串中,准备加到新字符串的那个字符;这两个字符当中,只要有一个不是空格,就可以加到新的字符串当中
|
||||
if char != " " || lastArr[lastArr.count - 1] != " " {
|
||||
lastArr.append(char)
|
||||
}
|
||||
|
||||
left += 1
|
||||
}
|
||||
return lastArr
|
||||
}
|
||||
|
||||
/// 2、反转整个字符串
|
||||
func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) {
|
||||
var start = startIndex
|
||||
var end = endIndex
|
||||
while start < end {
|
||||
(s[start], s[end]) = (s[end], s[start])
|
||||
start += 1
|
||||
end -= 1
|
||||
}
|
||||
}
|
||||
|
||||
/// 3、再次将字符串里面的单词反转
|
||||
func reverseWord(_ s: inout [Character]) {
|
||||
var start = 0
|
||||
var end = 0
|
||||
var entry = false
|
||||
|
||||
for i in 0..<s.count {
|
||||
if !entry {
|
||||
start = i
|
||||
entry = true
|
||||
}
|
||||
|
||||
if entry && s[i] == " " && s[i - 1] != " " {
|
||||
end = i - 1
|
||||
entry = false
|
||||
reverseString(&s, startIndex: start, endIndex: end)
|
||||
}
|
||||
|
||||
if entry && (i == s.count - 1) && s[i] != " " {
|
||||
end = i
|
||||
entry = false
|
||||
reverseString(&s, startIndex: start, endIndex: end)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -191,7 +191,37 @@ var isHappy = function(n) {
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
Swift:
|
||||
```swift
|
||||
// number 每个位置上的数字的平方和
|
||||
func getSum(_ number: Int) -> Int {
|
||||
var sum = 0
|
||||
var num = number
|
||||
while num > 0 {
|
||||
let temp = num % 10
|
||||
sum += (temp * temp)
|
||||
num /= 10
|
||||
}
|
||||
return sum
|
||||
}
|
||||
func isHappy(_ n: Int) -> Bool {
|
||||
var set = Set<Int>()
|
||||
var num = n
|
||||
while true {
|
||||
let sum = self.getSum(num)
|
||||
if sum == 1 {
|
||||
return true
|
||||
}
|
||||
// 如果这个sum曾经出现过,说明已经陷入了无限循环了
|
||||
if set.contains(sum) {
|
||||
return false
|
||||
} else {
|
||||
set.insert(sum)
|
||||
}
|
||||
num = sum
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -97,6 +97,23 @@ class Solution {
|
||||
## Go
|
||||
|
||||
```go
|
||||
func isIsomorphic(s string, t string) bool {
|
||||
map1 := make(map[byte]byte)
|
||||
map2 := make(map[byte]byte)
|
||||
for i := range s {
|
||||
if _, ok := map1[s[i]]; !ok {
|
||||
map1[s[i]] = t[i] // map1保存 s[i] 到 t[j]的映射
|
||||
}
|
||||
if _, ok := map2[t[i]]; !ok {
|
||||
map2[t[i]] = s[i] // map2保存 t[i] 到 s[j]的映射
|
||||
}
|
||||
// 无法映射,返回 false
|
||||
if (map1[s[i]] != t[i]) || (map2[t[i]] != s[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
@ -237,6 +237,32 @@ func minSubArrayLen(_ target: Int, _ nums: [Int]) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
Rust:
|
||||
|
||||
```rust
|
||||
impl Solution {
|
||||
pub fn min_sub_array_len(target: i32, nums: Vec<i32>) -> i32 {
|
||||
let (mut result, mut subLength): (i32, i32) = (i32::MAX, 0);
|
||||
let (mut sum, mut i) = (0, 0);
|
||||
|
||||
for (pos, val) in nums.iter().enumerate() {
|
||||
sum += val;
|
||||
while sum >= target {
|
||||
subLength = (pos - i + 1) as i32;
|
||||
if result > subLength {
|
||||
result = subLength;
|
||||
}
|
||||
sum -= nums[i];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if result == i32::MAX {
|
||||
return 0;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
-----------------------
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
# 225. 用队列实现栈
|
||||
|
||||
https://leetcode-cn.com/problems/implement-stack-using-queues/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/implement-stack-using-queues/)
|
||||
|
||||
使用队列实现栈的下列操作:
|
||||
|
||||
@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/implement-stack-using-queues/
|
||||
|
||||
有的同学可能疑惑这种题目有什么实际工程意义,**其实很多算法题目主要是对知识点的考察和教学意义远大于其工程实践的意义,所以面试题也是这样!**
|
||||
|
||||
刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/Cj6R0qu8rFA7Et9V_ZMjCA)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!
|
||||
刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://programmercarl.com/0232.用栈实现队列.html)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行!
|
||||
|
||||
**队列模拟栈,其实一个队列就够了**,那么我们先说一说两个队列来实现栈的思路。
|
||||
|
||||
@ -359,6 +359,71 @@ class MyStack:
|
||||
|
||||
Go:
|
||||
|
||||
```go
|
||||
type MyStack struct {
|
||||
queue []int//创建一个队列
|
||||
}
|
||||
|
||||
|
||||
/** Initialize your data structure here. */
|
||||
func Constructor() MyStack {
|
||||
return MyStack{ //初始化
|
||||
queue:make([]int,0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Push element x onto stack. */
|
||||
func (this *MyStack) Push(x int) {
|
||||
//添加元素
|
||||
this.queue=append(this.queue,x)
|
||||
}
|
||||
|
||||
|
||||
/** Removes the element on top of the stack and returns that element. */
|
||||
func (this *MyStack) Pop() int {
|
||||
n:=len(this.queue)-1//判断长度
|
||||
for n!=0{ //除了最后一个,其余的都重新添加到队列里
|
||||
val:=this.queue[0]
|
||||
this.queue=this.queue[1:]
|
||||
this.queue=append(this.queue,val)
|
||||
n--
|
||||
}
|
||||
//弹出元素
|
||||
val:=this.queue[0]
|
||||
this.queue=this.queue[1:]
|
||||
return val
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Get the top element. */
|
||||
func (this *MyStack) Top() int {
|
||||
//利用Pop函数,弹出来的元素重新添加
|
||||
val:=this.Pop()
|
||||
this.queue=append(this.queue,val)
|
||||
return val
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether the stack is empty. */
|
||||
func (this *MyStack) Empty() bool {
|
||||
return len(this.queue)==0
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Your MyStack object will be instantiated and called as such:
|
||||
* obj := Constructor();
|
||||
* obj.Push(x);
|
||||
* param_2 := obj.Pop();
|
||||
* param_3 := obj.Top();
|
||||
* param_4 := obj.Empty();
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
javaScript:
|
||||
|
||||
使用数组(push, shift)模拟队列
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
# 232.用栈实现队列
|
||||
|
||||
https://leetcode-cn.com/problems/implement-queue-using-stacks/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/implement-queue-using-stacks/)
|
||||
|
||||
使用栈实现队列的下列操作:
|
||||
|
||||
@ -205,33 +205,26 @@ class MyQueue:
|
||||
|
||||
def pop(self) -> int:
|
||||
"""
|
||||
1. 检查如果out里面元素,则直接pop
|
||||
2. 如果out没有元素,就把in里面的元素(除了第一个)依次pop后装进out里面
|
||||
3. 直接把in剩下的元素pop出来,就是queue头部的
|
||||
Removes the element from in front of queue and returns that element.
|
||||
"""
|
||||
if self.empty:
|
||||
if self.empty():
|
||||
return None
|
||||
|
||||
if self.stack_out:
|
||||
return self.stack_out.pop()
|
||||
else:
|
||||
for i in range(1, len(self.stack_in)):
|
||||
for i in range(len(self.stack_in)):
|
||||
self.stack_out.append(self.stack_in.pop())
|
||||
return self.stack_in.pop()
|
||||
return self.stack_out.pop()
|
||||
|
||||
|
||||
def peek(self) -> int:
|
||||
"""
|
||||
1. 查out有没有元素,有就把最上面的返回
|
||||
2. 如果out没有元素,就把in最下面的返回
|
||||
Get the front element.
|
||||
"""
|
||||
if self.empty:
|
||||
return None
|
||||
|
||||
if self.stack_out:
|
||||
return self.stack_out[-1]
|
||||
else:
|
||||
return self.stack_in[0]
|
||||
ans = self.pop()
|
||||
self.stack_out.append(ans)
|
||||
return ans
|
||||
|
||||
|
||||
def empty(self) -> bool:
|
||||
|
@ -144,6 +144,75 @@ public:
|
||||
## Java
|
||||
|
||||
```java
|
||||
// 方法一,使用数组
|
||||
class Solution {
|
||||
public boolean isPalindrome(ListNode head) {
|
||||
int len = 0;
|
||||
// 统计链表长度
|
||||
ListNode cur = head;
|
||||
while (cur != null) {
|
||||
len++;
|
||||
cur = cur.next;
|
||||
}
|
||||
cur = head;
|
||||
int[] res = new int[len];
|
||||
// 将元素加到数组之中
|
||||
for (int i = 0; i < res.length; i++){
|
||||
res[i] = cur.val;
|
||||
cur = cur.next;
|
||||
}
|
||||
// 比较回文
|
||||
for (int i = 0, j = len - 1; i < j; i++, j--){
|
||||
if (res[i] != res[j]){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 方法二,快慢指针
|
||||
class Solution {
|
||||
public boolean isPalindrome(ListNode head) {
|
||||
// 如果为空或者仅有一个节点,返回true
|
||||
if (head == null && head.next == null) return true;
|
||||
ListNode slow = head;
|
||||
ListNode fast = head;
|
||||
ListNode pre = head;
|
||||
while (fast != null && fast.next != null){
|
||||
pre = slow; // 记录slow的前一个结点
|
||||
slow = slow.next;
|
||||
fast = fast.next.next;
|
||||
}
|
||||
pre.next = null; // 分割两个链表
|
||||
|
||||
// 前半部分
|
||||
ListNode cur1 = head;
|
||||
// 后半部分。这里使用了反转链表
|
||||
ListNode cur2 = reverseList(slow);
|
||||
|
||||
while (cur1 != null){
|
||||
if (cur1.val != cur2.val) return false;
|
||||
|
||||
// 注意要移动两个结点
|
||||
cur1 = cur1.next;
|
||||
cur2 = cur2.next;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ListNode reverseList(ListNode head){
|
||||
// 反转链表
|
||||
ListNode tmp = null;
|
||||
ListNode pre = null;
|
||||
while (head != null){
|
||||
tmp = head.next;
|
||||
head.next = pre;
|
||||
pre = head;
|
||||
head = tmp;
|
||||
}
|
||||
return pre;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
@ -209,11 +278,13 @@ class Solution:
|
||||
## Go
|
||||
|
||||
```go
|
||||
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
||||
```js
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<p align="center"><strong>欢迎大家<a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
|
||||
|
||||
|
||||
## 235. 二叉搜索树的最近公共祖先
|
||||
# 235. 二叉搜索树的最近公共祖先
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/)
|
||||
|
||||
@ -21,14 +21,15 @@
|
||||
|
||||
示例 1:
|
||||
|
||||
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
|
||||
输出: 6
|
||||
解释: 节点 2 和节点 8 的最近公共祖先是 6。
|
||||
* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
|
||||
* 输出: 6
|
||||
* 解释: 节点 2 和节点 8 的最近公共祖先是 6。
|
||||
|
||||
示例 2:
|
||||
|
||||
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
|
||||
输出: 2
|
||||
解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
|
||||
* 输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
|
||||
* 输出: 2
|
||||
* 解释: 节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
|
||||
|
||||
|
||||
说明:
|
||||
@ -36,7 +37,9 @@
|
||||
* 所有节点的值都是唯一的。
|
||||
* p、q 为不同节点且均存在于给定的二叉搜索树中。
|
||||
|
||||
## 思路
|
||||
# 思路
|
||||
|
||||
|
||||
|
||||
做过[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)题目的同学应该知道,利用回溯从底向上搜索,遇到一个节点的左子树里有p,右子树里有q,那么当前节点就是最近公共祖先。
|
||||
|
||||
@ -58,6 +61,7 @@
|
||||
|
||||
可以看出直接按照指定的方向,就可以找到节点4,为最近公共祖先,而且不需要遍历整棵树,找到结果直接返回!
|
||||
|
||||
## 递归法
|
||||
|
||||
递归三部曲如下:
|
||||
|
||||
@ -111,7 +115,6 @@ if (cur->val > p->val && cur->val > q->val) {
|
||||
|
||||
```
|
||||
if (递归函数(root->left)) return ;
|
||||
|
||||
if (递归函数(root->right)) return ;
|
||||
```
|
||||
|
||||
@ -128,7 +131,7 @@ left与right的逻辑处理;
|
||||
|
||||
如果 cur->val 小于 p->val,同时 cur->val 小于 q->val,那么就应该向右遍历(目标区间在右子树)。
|
||||
|
||||
```
|
||||
```CPP
|
||||
if (cur->val < p->val && cur->val < q->val) {
|
||||
TreeNode* right = traversal(cur->right, p, q);
|
||||
if (right != NULL) {
|
||||
@ -140,9 +143,9 @@ if (cur->val < p->val && cur->val < q->val) {
|
||||
剩下的情况,就是cur节点在区间(p->val <= cur->val && cur->val <= q->val)或者 (q->val <= cur->val && cur->val <= p->val)中,那么cur就是最近公共祖先了,直接返回cur。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
return cur;
|
||||
|
||||
```
|
||||
|
||||
那么整体递归代码如下:
|
||||
@ -216,7 +219,7 @@ public:
|
||||
|
||||
灵魂拷问:是不是又被简单的迭代法感动到痛哭流涕?
|
||||
|
||||
## 总结
|
||||
# 总结
|
||||
|
||||
对于二叉搜索树的最近祖先问题,其实要比[普通二叉树公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)简单的多。
|
||||
|
||||
@ -225,10 +228,15 @@ public:
|
||||
最后给出了对应的迭代法,二叉搜索树的迭代法甚至比递归更容易理解,也是因为其有序性(自带方向性),按照目标区间找就行了。
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
# 其他语言版本
|
||||
|
||||
|
||||
Java:
|
||||
## Java
|
||||
|
||||
递归法:
|
||||
|
||||
|
||||
迭代法:
|
||||
```java
|
||||
class Solution {
|
||||
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
@ -246,15 +254,11 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
```python
|
||||
# Definition for a binary tree node.
|
||||
# class TreeNode:
|
||||
# def __init__(self, x):
|
||||
# self.val = x
|
||||
# self.left = None
|
||||
# self.right = None
|
||||
|
||||
## Python
|
||||
|
||||
递归法:
|
||||
```python
|
||||
class Solution:
|
||||
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
|
||||
if not root: return root //中
|
||||
@ -264,18 +268,14 @@ class Solution:
|
||||
return self.lowestCommonAncestor(root.right,p,q) //右
|
||||
else: return root
|
||||
```
|
||||
Go:
|
||||
> BSL法
|
||||
|
||||
迭代法:
|
||||
|
||||
|
||||
## Go
|
||||
|
||||
递归法:
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
//利用BSL的性质(前序遍历有序)
|
||||
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
if root==nil{return nil}
|
||||
@ -287,34 +287,10 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
}
|
||||
```
|
||||
|
||||
> 普通法
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
//递归会将值层层返回
|
||||
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
//终止条件
|
||||
if root==nil||root.Val==p.Val||root.Val==q.Val{return root}//最后为空或者找到一个值时,就返回这个值
|
||||
//后序遍历
|
||||
findLeft:=lowestCommonAncestor(root.Left,p,q)
|
||||
findRight:=lowestCommonAncestor(root.Right,p,q)
|
||||
//处理单层逻辑
|
||||
if findLeft!=nil&&findRight!=nil{return root}//说明在root节点的两边
|
||||
if findLeft==nil{//左边没找到,就说明在右边找到了
|
||||
return findRight
|
||||
}else {return findLeft}
|
||||
}
|
||||
```
|
||||
## JavaScript
|
||||
|
||||
JavaScript版本:
|
||||
1. 使用递归的方法
|
||||
递归法:
|
||||
```javascript
|
||||
var lowestCommonAncestor = function(root, p, q) {
|
||||
// 使用递归的方法
|
||||
@ -336,7 +312,8 @@ var lowestCommonAncestor = function(root, p, q) {
|
||||
return root;
|
||||
};
|
||||
```
|
||||
2. 使用迭代的方法
|
||||
|
||||
迭代法
|
||||
```javascript
|
||||
var lowestCommonAncestor = function(root, p, q) {
|
||||
// 使用迭代的方法
|
||||
@ -355,7 +332,6 @@ var lowestCommonAncestor = function(root, p, q) {
|
||||
```
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
> 本来是打算将二叉树和二叉搜索树的公共祖先问题一起讲,后来发现篇幅过长了,只能先说一说二叉树的公共祖先问题。
|
||||
|
||||
## 236. 二叉树的最近公共祖先
|
||||
# 236. 二叉树的最近公共祖先
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/)
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
* 所有节点的值都是唯一的。
|
||||
* p、q 为不同节点且均存在于给定的二叉树中。
|
||||
|
||||
## 思路
|
||||
# 思路
|
||||
|
||||
遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。
|
||||
|
||||
@ -202,7 +202,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
## 总结
|
||||
# 总结
|
||||
|
||||
这道题目刷过的同学未必真正了解这里面回溯的过程,以及结果是如何一层一层传上去的。
|
||||
|
||||
@ -219,10 +219,10 @@ public:
|
||||
本题没有给出迭代法,因为迭代法不适合模拟回溯的过程。理解递归的解法就够了。
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
# 其他语言版本
|
||||
|
||||
|
||||
Java:
|
||||
## Java
|
||||
```Java
|
||||
class Solution {
|
||||
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||
@ -261,14 +261,9 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
## Python
|
||||
|
||||
```python
|
||||
# Definition for a binary tree node.
|
||||
# class TreeNode:
|
||||
# def __init__(self, x):
|
||||
# self.val = x
|
||||
# self.left = None
|
||||
# self.right = None
|
||||
//递归
|
||||
class Solution:
|
||||
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
|
||||
@ -280,7 +275,9 @@ class Solution:
|
||||
elif not left and right: return right //目标节点是通过right返回的
|
||||
else: return None //没找到
|
||||
```
|
||||
Go:
|
||||
|
||||
## Go
|
||||
|
||||
```Go
|
||||
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
// check
|
||||
@ -310,7 +307,8 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
|
||||
}
|
||||
```
|
||||
|
||||
JavaScript版本:
|
||||
## JavaScript
|
||||
|
||||
```javascript
|
||||
var lowestCommonAncestor = function(root, p, q) {
|
||||
// 使用递归的方法
|
||||
|
@ -198,6 +198,29 @@ var isAnagram = function(s, t) {
|
||||
};
|
||||
```
|
||||
|
||||
Swift:
|
||||
```Swift
|
||||
func isAnagram(_ s: String, _ t: String) -> Bool {
|
||||
if s.count != t.count {
|
||||
return false
|
||||
}
|
||||
var record = Array(repeating: 0, count: 26)
|
||||
let aUnicodeScalar = "a".unicodeScalars.first!.value
|
||||
for c in s.unicodeScalars {
|
||||
record[Int(c.value - aUnicodeScalar)] += 1
|
||||
}
|
||||
for c in t.unicodeScalars {
|
||||
record[Int(c.value - aUnicodeScalar)] -= 1
|
||||
}
|
||||
for value in record {
|
||||
if value != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
## 相关题目
|
||||
|
||||
* 383.赎金信
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
## 279.完全平方数
|
||||
|
||||
题目地址:https://leetcode-cn.com/problems/perfect-squares/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/perfect-squares/)
|
||||
|
||||
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
|
||||
**我来把题目翻译一下:完全平方数就是物品(可以无限件使用),凑个正整数n就是背包,问凑满这个背包最少有多少物品?**
|
||||
|
||||
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)就是一样一样的!
|
||||
感受出来了没,这么浓厚的完全背包氛围,而且和昨天的题目[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)就是一样一样的!
|
||||
|
||||
动规五部曲分析如下:
|
||||
|
||||
@ -70,7 +70,7 @@ dp[0]表示 和为0的完全平方数的最小数量,那么dp[0]一定是0。
|
||||
|
||||
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
|
||||
|
||||
在[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
|
||||
在[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)中我们就深入探讨了这个问题,本题也是一样的,是求最小数!
|
||||
|
||||
**所以本题外层for遍历背包,里层for遍历物品,还是外层for遍历物品,内层for遍历背包,都是可以的!**
|
||||
|
||||
@ -146,7 +146,7 @@ public:
|
||||
|
||||
## 总结
|
||||
|
||||
如果大家认真做了昨天的题目[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ),今天这道就非常简单了,一样的套路一样的味道。
|
||||
如果大家认真做了昨天的题目[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html),今天这道就非常简单了,一样的套路一样的味道。
|
||||
|
||||
但如果没有按照「代码随想录」的题目顺序来做的话,做动态规划或者做背包问题,上来就做这道题,那还是挺难的!
|
||||
|
||||
|
@ -8,13 +8,13 @@
|
||||
|
||||
## 300.最长递增子序列
|
||||
|
||||
题目链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/longest-increasing-subsequence/)
|
||||
|
||||
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
|
||||
|
||||
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
|
||||
|
||||
|
||||
|
||||
示例 1:
|
||||
输入:nums = [10,9,2,5,3,7,101,18]
|
||||
输出:4
|
||||
@ -27,7 +27,7 @@
|
||||
示例 3:
|
||||
输入:nums = [7,7,7,7,7,7,7]
|
||||
输出:1
|
||||
|
||||
|
||||
提示:
|
||||
|
||||
* 1 <= nums.length <= 2500
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
与198.打家劫舍,213.打家劫舍II一样,关键是要讨论当前节点抢还是不抢。
|
||||
|
||||
如果抢了当前节点,两个孩子就不是动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**)
|
||||
如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子(**注意这里说的是“考虑”**)
|
||||
|
||||
### 暴力递归
|
||||
|
||||
@ -91,7 +91,7 @@ public:
|
||||
|
||||
### 动态规划
|
||||
|
||||
在上面两种方法,其实对一个节点 投与不投得到的最大金钱都没有做记录,而是需要实时计算。
|
||||
在上面两种方法,其实对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算。
|
||||
|
||||
而动态规划其实就是使用状态转移容器来记录状态的变化,这里可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。
|
||||
|
||||
@ -121,7 +121,7 @@ vector<int> robTree(TreeNode* cur) {
|
||||
|
||||
2. 确定终止条件
|
||||
|
||||
在遍历的过程中,如果遇到空间点的话,很明显,无论偷还是不偷都是0,所以就返回
|
||||
在遍历的过程中,如果遇到空节点的话,很明显,无论偷还是不偷都是0,所以就返回
|
||||
```
|
||||
if (cur == NULL) return vector<int>{0, 0};
|
||||
```
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
# 344.反转字符串
|
||||
|
||||
https://leetcode-cn.com/problems/reverse-string/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/reverse-string/)
|
||||
|
||||
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
|
||||
|
||||
@ -55,7 +55,7 @@ https://leetcode-cn.com/problems/reverse-string/
|
||||
|
||||
接下来再来讲一下如何解决反转字符串的问题。
|
||||
|
||||
大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)。
|
||||
大家应该还记得,我们已经讲过了[206.反转链表](https://programmercarl.com/0206.翻转链表.html)。
|
||||
|
||||
在反转链表中,使用了双指针的方法。
|
||||
|
||||
@ -63,7 +63,7 @@ https://leetcode-cn.com/problems/reverse-string/
|
||||
|
||||
因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。
|
||||
|
||||
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw),[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。
|
||||
如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html),[必须掌握的数组理论知识](https://programmercarl.com/数组理论基础.html)。
|
||||
|
||||
对于字符串,我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
## 349. 两个数组的交集
|
||||
|
||||
https://leetcode-cn.com/problems/intersection-of-two-arrays/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays/)
|
||||
|
||||
题意:给定两个数组,编写一个函数来计算它们的交集。
|
||||
|
||||
@ -32,7 +32,7 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/
|
||||
|
||||
这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。
|
||||
|
||||
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)
|
||||
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)
|
||||
|
||||
但是要注意,**使用数组来做哈希的题目,是因为题目都限制了数值的大小。**
|
||||
|
||||
@ -192,6 +192,22 @@ var intersection = function(nums1, nums2) {
|
||||
};
|
||||
```
|
||||
|
||||
Swift:
|
||||
```swift
|
||||
func intersection(_ nums1: [Int], _ nums2: [Int]) -> [Int] {
|
||||
var set1 = Set<Int>()
|
||||
var set2 = Set<Int>()
|
||||
for num in nums1 {
|
||||
set1.insert(num)
|
||||
}
|
||||
for num in nums2 {
|
||||
if set1.contains(num) {
|
||||
set2.insert(num)
|
||||
}
|
||||
}
|
||||
return Array(set2)
|
||||
}
|
||||
```
|
||||
|
||||
## 相关题目
|
||||
|
||||
|
@ -203,6 +203,25 @@ const isSubsequence = (s, t) => {
|
||||
};
|
||||
```
|
||||
|
||||
Go:
|
||||
```go
|
||||
func isSubsequence(s string, t string) bool {
|
||||
dp := make([][]int,len(s)+1)
|
||||
for i:=0;i<len(dp);i++{
|
||||
dp[i] = make([]int,len(t)+1)
|
||||
}
|
||||
for i:=1;i<len(dp);i++{
|
||||
for j:=1;j<len(dp[i]);j++{
|
||||
if s[i-1] == t[j-1]{
|
||||
dp[i][j] = dp[i-1][j-1] +1
|
||||
}else{
|
||||
dp[i][j] = dp[i][j-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[len(s)][len(t)]==len(s)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
@ -218,6 +218,30 @@ var findMinArrowShots = function(points) {
|
||||
};
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
int cmp(const void *a,const void *b)
|
||||
{
|
||||
return ((*((int**)a))[0] > (*((int**)b))[0]);
|
||||
}
|
||||
|
||||
int findMinArrowShots(int** points, int pointsSize, int* pointsColSize){
|
||||
//将points数组作升序排序
|
||||
qsort(points, pointsSize, sizeof(points[0]),cmp);
|
||||
|
||||
int arrowNum = 1;
|
||||
int i = 1;
|
||||
for(i = 1; i < pointsSize; i++) {
|
||||
//若前一个气球与当前气球不重叠,证明需要增加箭的数量
|
||||
if(points[i][0] > points[i-1][1])
|
||||
arrowNum++;
|
||||
else
|
||||
//若前一个气球与当前气球重叠,判断并更新最小的x_end
|
||||
points[i][1] = points[i][1] > points[i-1][1] ? points[i-1][1] : points[i][1];
|
||||
}
|
||||
return arrowNum;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -161,15 +161,9 @@ class Solution(object):
|
||||
# count=0
|
||||
# for x3 in nums3:
|
||||
# for x4 in nums4:
|
||||
# key = -x3-x4
|
||||
# value = hashmap.get(key)
|
||||
|
||||
# dict的get方法会返回None(key不存在)或者key对应的value
|
||||
# 所以如果value==0,就会继续执行or,count+0,否则就会直接加value
|
||||
# 这样就不用去写if判断了
|
||||
|
||||
# count += value or 0
|
||||
|
||||
# key = 0 - x3 - x4
|
||||
# value = hashmap[key] # 若差值(key)不存在,则value被赋值0
|
||||
# count += value
|
||||
# return count
|
||||
|
||||
```
|
||||
|
@ -117,6 +117,7 @@ public:
|
||||
Java:
|
||||
```java
|
||||
class Solution {
|
||||
// 思路1:优先考虑饼干,小饼干先喂饱小胃口
|
||||
public int findContentChildren(int[] g, int[] s) {
|
||||
Arrays.sort(g);
|
||||
Arrays.sort(s);
|
||||
@ -132,10 +133,30 @@ class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
```java
|
||||
class Solution {
|
||||
// 思路2:优先考虑胃口,先喂饱大胃口
|
||||
public int findContentChildren(int[] g, int[] s) {
|
||||
Arrays.sort(g);
|
||||
Arrays.sort(s);
|
||||
int count = 0;
|
||||
int start = s.length - 1;
|
||||
// 遍历胃口
|
||||
for (int index = g.length - 1; index >= 0; index--) {
|
||||
if(start >= 0 && g[index] <= s[start]) {
|
||||
start--;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
```python3
|
||||
class Solution:
|
||||
# 思路1:优先考虑胃饼干
|
||||
def findContentChildren(self, g: List[int], s: List[int]) -> int:
|
||||
g.sort()
|
||||
s.sort()
|
||||
@ -145,6 +166,20 @@ class Solution:
|
||||
res += 1
|
||||
return res
|
||||
```
|
||||
```python
|
||||
class Solution:
|
||||
# 思路2:优先考虑胃口
|
||||
def findContentChildren(self, g: List[int], s: List[int]) -> int:
|
||||
g.sort()
|
||||
s.sort()
|
||||
start, count = len(s) - 1, 0
|
||||
for index in range(len(g) - 1, -1, -1): # 先喂饱大胃口
|
||||
if start >= 0 and g[index] <= s[start]:
|
||||
start -= 1
|
||||
count += 1
|
||||
return count
|
||||
```
|
||||
|
||||
Go:
|
||||
```golang
|
||||
//排序后,局部最优
|
||||
|
@ -128,7 +128,10 @@ x = (S + sum) / 2
|
||||
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
|
||||
```
|
||||
|
||||
**看到这种表达式,应该本能的反应,两个int相加数值可能溢出的问题,当然本题并没有溢出**。
|
||||
同时如果 S的绝对值已经大于sum,那么也是没有方案的。
|
||||
```CPP
|
||||
if (abs(S) > sum) return 0; // 此时没有方案
|
||||
```
|
||||
|
||||
再回归到01背包问题,为什么是01背包呢?
|
||||
|
||||
@ -200,7 +203,7 @@ public:
|
||||
int findTargetSumWays(vector<int>& nums, int S) {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < nums.size(); i++) sum += nums[i];
|
||||
if (S > sum) return 0; // 此时没有方案
|
||||
if (abs(S) > sum) return 0; // 此时没有方案
|
||||
if ((S + sum) % 2 == 1) return 0; // 此时没有方案
|
||||
int bagSize = (S + sum) / 2;
|
||||
vector<int> dp(bagSize + 1, 0);
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
> 二叉树上应该怎么求,二叉搜索树上又应该怎么求?
|
||||
|
||||
## 501.二叉搜索树中的众数
|
||||
# 501.二叉搜索树中的众数
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/solution/)
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
|
||||
进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
|
||||
|
||||
## 思路
|
||||
# 思路
|
||||
|
||||
这道题目呢,递归法我从两个维度来讲。
|
||||
|
||||
@ -321,7 +321,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
## 总结
|
||||
# 总结
|
||||
|
||||
本题在递归法中,我给出了如果是普通二叉树,应该怎么求众数。
|
||||
|
||||
@ -340,12 +340,13 @@ public:
|
||||
> **需要强调的是 leetcode上的耗时统计是非常不准确的,看个大概就行,一样的代码耗时可以差百分之50以上**,所以leetcode的耗时统计别太当回事,知道理论上的效率优劣就行了。
|
||||
|
||||
|
||||
## 其他语言版本
|
||||
# 其他语言版本
|
||||
|
||||
|
||||
Java:
|
||||
## Java
|
||||
|
||||
暴力法
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int[] findMode(FindModeInBinarySearchTree.TreeNode root) {
|
||||
@ -379,6 +380,8 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
中序遍历-不使用额外空间,利用二叉搜索树特性
|
||||
|
||||
```Java
|
||||
class Solution {
|
||||
ArrayList<Integer> resList;
|
||||
@ -427,15 +430,11 @@ class Solution {
|
||||
}
|
||||
```
|
||||
|
||||
Python:
|
||||
## Python
|
||||
|
||||
递归法
|
||||
|
||||
```python
|
||||
# Definition for a binary tree node.
|
||||
# class TreeNode:
|
||||
# def __init__(self, val=0, left=None, right=None):
|
||||
# self.val = val
|
||||
# self.left = left
|
||||
# self.right = right
|
||||
# 递归法
|
||||
class Solution:
|
||||
def findMode(self, root: TreeNode) -> List[int]:
|
||||
if not root: return
|
||||
@ -460,36 +459,11 @@ class Solution:
|
||||
return
|
||||
findNumber(root)
|
||||
return self.res
|
||||
```
|
||||
|
||||
|
||||
# 迭代法-中序遍历-使用额外空间map的方法:
|
||||
class Solution:
|
||||
def findMode(self, root: TreeNode) -> List[int]:
|
||||
stack = []
|
||||
cur = root
|
||||
pre = None
|
||||
dist = {}
|
||||
while cur or stack:
|
||||
if cur: # 指针来访问节点,访问到最底层
|
||||
stack.append(cur)
|
||||
cur = cur.left
|
||||
else: # 逐一处理节点
|
||||
cur = stack.pop()
|
||||
if cur.val in dist:
|
||||
dist[cur.val] += 1
|
||||
else:
|
||||
dist[cur.val] = 1
|
||||
pre = cur
|
||||
cur = cur.right
|
||||
|
||||
# 找出字典中最大的key
|
||||
res = []
|
||||
for key, value in dist.items():
|
||||
if (value == max(dist.values())):
|
||||
res.append(key)
|
||||
return res
|
||||
|
||||
# 迭代法-中序遍历-不使用额外空间,利用二叉搜索树特性:
|
||||
迭代法-中序遍历-不使用额外空间,利用二叉搜索树特性
|
||||
```python
|
||||
class Solution:
|
||||
def findMode(self, root: TreeNode) -> List[int]:
|
||||
stack = []
|
||||
@ -521,18 +495,11 @@ class Solution:
|
||||
return res
|
||||
|
||||
```
|
||||
Go:
|
||||
## Go
|
||||
|
||||
暴力法(非BSL)
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
func findMode(root *TreeNode) []int {
|
||||
var history map[int]int
|
||||
var maxValue int
|
||||
@ -571,15 +538,7 @@ func traversal(root *TreeNode,history map[int]int){
|
||||
计数法,不使用额外空间,利用二叉树性质,中序遍历
|
||||
|
||||
```go
|
||||
/**
|
||||
* Definition for a binary tree node.
|
||||
* type TreeNode struct {
|
||||
* Val int
|
||||
* Left *TreeNode
|
||||
* Right *TreeNode
|
||||
* }
|
||||
*/
|
||||
func findMode(root *TreeNode) []int {
|
||||
func findMode(root *TreeNode) []int {
|
||||
res := make([]int, 0)
|
||||
count := 1
|
||||
max := 1
|
||||
@ -611,8 +570,9 @@ func traversal(root *TreeNode,history map[int]int){
|
||||
}
|
||||
```
|
||||
|
||||
JavaScript版本:
|
||||
使用额外空间map的方法:
|
||||
## JavaScript
|
||||
|
||||
使用额外空间map的方法
|
||||
```javascript
|
||||
var findMode = function(root) {
|
||||
// 使用递归中序遍历
|
||||
@ -649,8 +609,10 @@ var findMode = function(root) {
|
||||
}
|
||||
return res;
|
||||
};
|
||||
```
|
||||
```
|
||||
|
||||
不使用额外空间,利用二叉树性质,中序遍历(有序):
|
||||
|
||||
```javascript
|
||||
var findMode = function(root) {
|
||||
// 不使用额外空间,使用中序遍历,设置出现最大次数初始值为1
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
# 541. 反转字符串II
|
||||
|
||||
https://leetcode-cn.com/problems/reverse-string-ii/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/reverse-string-ii/)
|
||||
|
||||
给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
|
||||
|
||||
@ -65,7 +65,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)道理是一样的。
|
||||
那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://programmercarl.com/0344.反转字符串.html)道理是一样的。
|
||||
|
||||
下面我实现的reverse函数区间是左闭右闭区间,代码如下:
|
||||
|
||||
@ -152,7 +152,35 @@ class Solution {
|
||||
}
|
||||
}
|
||||
```
|
||||
```java
|
||||
// 解法3
|
||||
class Solution {
|
||||
public String reverseStr(String s, int k) {
|
||||
char[] ch = s.toCharArray();
|
||||
// 1. 每隔 2k 个字符的前 k 个字符进行反转
|
||||
for (int i = 0; i< ch.length; i += 2 * k) {
|
||||
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
|
||||
if (i + k <= ch.length) {
|
||||
reverse(ch, i, i + k -1);
|
||||
continue;
|
||||
}
|
||||
// 3. 剩余字符少于 k 个,则将剩余字符全部反转
|
||||
reverse(ch, i, ch.length - 1);
|
||||
}
|
||||
return new String(ch);
|
||||
|
||||
}
|
||||
// 定义翻转函数
|
||||
public void reverse(char[] ch, int i, int j) {
|
||||
for (; i < j; i++, j--) {
|
||||
char temp = ch[i];
|
||||
ch[i] = ch[j];
|
||||
ch[j] = temp;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
Python:
|
||||
```python
|
||||
class Solution:
|
||||
@ -226,6 +254,28 @@ var reverseStr = function(s, k) {
|
||||
|
||||
```
|
||||
|
||||
Swift:
|
||||
|
||||
```swift
|
||||
func reverseStr(_ s: String, _ k: Int) -> String {
|
||||
var ch = Array(s)
|
||||
|
||||
for i in stride(from: 0, to: ch.count, by: 2 * k) {
|
||||
var left = i
|
||||
var right = min(s.count - 1, left + k - 1)
|
||||
|
||||
while left < right {
|
||||
(ch[left], ch[right]) = (ch[right], ch[left])
|
||||
left += 1
|
||||
right -= 1
|
||||
}
|
||||
}
|
||||
return String(ch)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
|
@ -147,8 +147,38 @@ class Solution:
|
||||
```
|
||||
|
||||
Go:
|
||||
```go
|
||||
func minDistance(word1 string, word2 string) int {
|
||||
dp := make([][]int, len(word1)+1)
|
||||
for i := 0; i < len(dp); i++ {
|
||||
dp[i] = make([]int, len(word2)+1)
|
||||
}
|
||||
//初始化
|
||||
for i := 0; i < len(dp); i++ {
|
||||
dp[i][0] = i
|
||||
}
|
||||
for j := 0; j < len(dp[0]); j++ {
|
||||
dp[0][j] = j
|
||||
}
|
||||
for i := 1; i < len(dp); i++ {
|
||||
for j := 1; j < len(dp[i]); j++ {
|
||||
if word1[i-1] == word2[j-1] {
|
||||
dp[i][j] = dp[i-1][j-1]
|
||||
} else {
|
||||
dp[i][j] = min(min(dp[i-1][j]+1, dp[i][j-1]+1), dp[i-1][j-1]+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return dp[len(dp)-1][len(dp[0])-1]
|
||||
}
|
||||
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
```
|
||||
Javascript:
|
||||
```javascript
|
||||
const minDistance = (word1, word2) => {
|
||||
|
@ -10,6 +10,9 @@
|
||||
|
||||
# 649. Dota2 参议院
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/dota2-senate/)
|
||||
|
||||
|
||||
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
|
||||
|
||||
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
|
||||
@ -115,16 +118,102 @@ public:
|
||||
## Java
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public String predictPartyVictory(String senateStr) {
|
||||
// R = true表示本轮循环结束后,字符串里依然有R。D同理
|
||||
Boolean R = true, D = true;
|
||||
// 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R
|
||||
int flag = 0;
|
||||
byte[] senate = senateStr.getBytes();
|
||||
while (R && D) { // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了
|
||||
R = false;
|
||||
D = false;
|
||||
for (int i = 0; i < senate.length; i++) {
|
||||
if (senate[i] == 'R') {
|
||||
if (flag < 0) senate[i] = 0; // 消灭R,R此时为false
|
||||
else R = true; // 如果没被消灭,本轮循环结束有R
|
||||
flag++;
|
||||
}
|
||||
if (senate[i] == 'D') {
|
||||
if (flag > 0) senate[i] = 0;
|
||||
else D = true;
|
||||
flag--;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 循环结束之后,R和D只能有一个为true
|
||||
return R == true ? "Radiant" : "Dire";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
```python
|
||||
class Solution:
|
||||
def predictPartyVictory(self, senate: str) -> str:
|
||||
# R = true表示本轮循环结束后,字符串里依然有R。D同理
|
||||
R , D = True, True
|
||||
|
||||
# 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R
|
||||
flag = 0
|
||||
|
||||
senate = list(senate)
|
||||
while R and D: # 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了
|
||||
R = False
|
||||
D = False
|
||||
for i in range(len(senate)) :
|
||||
if senate[i] == 'R' :
|
||||
if flag < 0: senate[i] = '0' # 消灭R,R此时为false
|
||||
else: R = True # 如果没被消灭,本轮循环结束有R
|
||||
flag += 1
|
||||
if senate[i] == 'D':
|
||||
if flag > 0: senate[i] = '0'
|
||||
else: D = True
|
||||
flag -= 1
|
||||
# 循环结束之后,R和D只能有一个为true
|
||||
return "Radiant" if R else "Dire"
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
```go
|
||||
|
||||
func predictPartyVictory(senateStr string) string {
|
||||
// R = true表示本轮循环结束后,字符串里依然有R。D同理
|
||||
R, D := true, true
|
||||
// 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R
|
||||
flag := 0
|
||||
|
||||
senate := []byte(senateStr)
|
||||
for R && D { // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了
|
||||
R = false
|
||||
D = false
|
||||
for i := 0; i < len(senate); i++ {
|
||||
if senate[i] == 'R' {
|
||||
if flag < 0 {
|
||||
senate[i] = 0 // 消灭R,R此时为false
|
||||
} else {
|
||||
R = true // 如果没被消灭,本轮循环结束有R
|
||||
}
|
||||
flag++;
|
||||
}
|
||||
if (senate[i] == 'D') {
|
||||
if flag > 0 {
|
||||
senate[i] = 0
|
||||
} else {
|
||||
D = true
|
||||
}
|
||||
flag--
|
||||
}
|
||||
}
|
||||
}
|
||||
// 循环结束之后,R和D只能有一个为true
|
||||
if R {
|
||||
return "Radiant"
|
||||
}
|
||||
return "Dire";
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
@ -9,6 +9,10 @@
|
||||
|
||||
# 673.最长递增子序列的个数
|
||||
|
||||
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/)
|
||||
|
||||
|
||||
给定一个未排序的整数数组,找到最长递增子序列的个数。
|
||||
|
||||
示例 1:
|
||||
@ -224,16 +228,110 @@ public:
|
||||
## Java
|
||||
|
||||
```java
|
||||
class Solution {
|
||||
public int findNumberOfLIS(int[] nums) {
|
||||
if (nums.length <= 1) return nums.length;
|
||||
int[] dp = new int[nums.length];
|
||||
for(int i = 0; i < dp.length; i++) dp[i] = 1;
|
||||
int[] count = new int[nums.length];
|
||||
for(int i = 0; i < count.length; i++) count[i] = 1;
|
||||
|
||||
int maxCount = 0;
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
for (int j = 0; j < i; j++) {
|
||||
if (nums[i] > nums[j]) {
|
||||
if (dp[j] + 1 > dp[i]) {
|
||||
dp[i] = dp[j] + 1;
|
||||
count[i] = count[j];
|
||||
} else if (dp[j] + 1 == dp[i]) {
|
||||
count[i] += count[j];
|
||||
}
|
||||
}
|
||||
if (dp[i] > maxCount) maxCount = dp[i];
|
||||
}
|
||||
}
|
||||
int result = 0;
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
if (maxCount == dp[i]) result += count[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
```python
|
||||
class Solution:
|
||||
def findNumberOfLIS(self, nums: List[int]) -> int:
|
||||
size = len(nums)
|
||||
if size<= 1: return size
|
||||
|
||||
dp = [1 for i in range(size)]
|
||||
count = [1 for i in range(size)]
|
||||
|
||||
maxCount = 0
|
||||
for i in range(1, size):
|
||||
for j in range(i):
|
||||
if nums[i] > nums[j]:
|
||||
if dp[j] + 1 > dp[i] :
|
||||
dp[i] = dp[j] + 1
|
||||
count[i] = count[j]
|
||||
elif dp[j] + 1 == dp[i] :
|
||||
count[i] += count[j]
|
||||
if dp[i] > maxCount:
|
||||
maxCount = dp[i];
|
||||
result = 0
|
||||
for i in range(size):
|
||||
if maxCount == dp[i]:
|
||||
result += count[i]
|
||||
return result;
|
||||
```
|
||||
|
||||
## Go
|
||||
|
||||
```go
|
||||
|
||||
func findNumberOfLIS(nums []int) int {
|
||||
size := len(nums)
|
||||
if size <= 1 {
|
||||
return size
|
||||
}
|
||||
|
||||
dp := make([]int, size);
|
||||
for i, _ := range dp {
|
||||
dp[i] = 1
|
||||
}
|
||||
count := make([]int, size);
|
||||
for i, _ := range count {
|
||||
count[i] = 1
|
||||
}
|
||||
|
||||
maxCount := 0
|
||||
for i := 1; i < size; i++ {
|
||||
for j := 0; j < i; j++ {
|
||||
if nums[i] > nums[j] {
|
||||
if dp[j] + 1 > dp[i] {
|
||||
dp[i] = dp[j] + 1
|
||||
count[i] = count[j]
|
||||
} else if dp[j] + 1 == dp[i] {
|
||||
count[i] += count[j]
|
||||
}
|
||||
}
|
||||
if dp[i] > maxCount {
|
||||
maxCount = dp[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := 0
|
||||
for i := 0; i < size; i++ {
|
||||
if maxCount == dp[i] {
|
||||
result += count[i]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
```
|
||||
|
||||
## JavaScript
|
||||
|
@ -374,7 +374,7 @@ func search(nums: [Int], target: Int) -> Int {
|
||||
} else if target > nums[middle] {
|
||||
// 当目标在区间右侧,就需要更新左边的边界值,新区间为[middle + 1, right]
|
||||
left = middle + 1
|
||||
} else {
|
||||
} else {
|
||||
// 当目标就是在中间,则返回中间值的下标
|
||||
return middle
|
||||
}
|
||||
@ -406,7 +406,77 @@ func search(nums: [Int], target: Int) -> Int {
|
||||
|
||||
```
|
||||
|
||||
**Rust:**
|
||||
|
||||
```rust
|
||||
# (版本一)左闭右闭区间
|
||||
|
||||
impl Solution {
|
||||
pub fn search(nums: Vec<i32>, target: i32) -> i32 {
|
||||
let mut left:usize = 0;
|
||||
let mut right:usize = nums.len() - 1;
|
||||
while left as i32 <= right as i32{
|
||||
let mid = (left + right) / 2;
|
||||
if nums[mid] < target {
|
||||
left = mid + 1;
|
||||
} else if nums[mid] > target {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
return mid as i32;
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
# (版本二)左闭右开区间
|
||||
|
||||
impl Solution {
|
||||
pub fn search(nums: Vec<i32>, target: i32) -> i32 {
|
||||
let mut left:usize = 0;
|
||||
let mut right:usize = nums.len();
|
||||
while left < right {
|
||||
let mid = (left + right) / 2;
|
||||
if nums[mid] < target {
|
||||
left = mid + 1;
|
||||
} else if nums[mid] > target {
|
||||
right = mid;
|
||||
} else {
|
||||
return mid as i32;
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**C:**
|
||||
```c
|
||||
int search(int* nums, int numsSize, int target){
|
||||
int left = 0;
|
||||
int right = numsSize-1;
|
||||
int middle = 0;
|
||||
//若left小于等于right,说明区间中元素不为0
|
||||
while(left<=right) {
|
||||
//更新查找下标middle的值
|
||||
middle = (left+right)/2;
|
||||
//此时target可能会在[left,middle-1]区间中
|
||||
if(nums[middle] > target) {
|
||||
right = middle-1;
|
||||
}
|
||||
//此时target可能会在[middle+1,right]区间中
|
||||
else if(nums[middle] < target) {
|
||||
left = middle+1;
|
||||
}
|
||||
//当前下标元素等于target值时,返回middle
|
||||
else if(nums[middle] == target){
|
||||
return middle;
|
||||
}
|
||||
}
|
||||
//若未找到target元素,返回-1
|
||||
return -1;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -406,6 +406,45 @@ var minCameraCover = function(root) {
|
||||
};
|
||||
```
|
||||
|
||||
C:
|
||||
```c
|
||||
/*
|
||||
**函数后序遍历二叉树。判断一个结点状态时,根据其左右孩子结点的状态进行判断
|
||||
**状态:0为没有被摄像头覆盖到。1为此结点处应设置摄像头。2为此结点已被摄像头覆盖
|
||||
*/
|
||||
int traversal(struct TreeNode* node, int* ans) {
|
||||
//递归结束条件:传入结点为NULL,假设此结点能被摄像头覆盖。这样方便与对叶子结点的判断,将叶子结点设为0
|
||||
if(!node)
|
||||
return 2;
|
||||
//后序遍历二叉树,记录左右孩子的状态。根据左右孩子状态更新结点自身状态
|
||||
int left = traversal(node->left, ans);
|
||||
int right = traversal(node->right, ans);
|
||||
|
||||
//若左右孩子都可以被摄像头覆盖,将父亲结点状态设为0
|
||||
if(left == 2 && right == 2) {
|
||||
return 0;
|
||||
}
|
||||
//若左右孩子有一个结点状态为没有被覆盖(0),则将父亲结点状态设置为摄像头
|
||||
if(left == 0 || right == 0) {
|
||||
(*ans)++;
|
||||
return 1;
|
||||
}
|
||||
//若左右孩子有一个为摄像头,证明父亲结点可以被覆盖。将父亲结点状态变为2
|
||||
if(left == 1 || right == 1)
|
||||
return 2;
|
||||
//逻辑不会走到-1,语句不会执行
|
||||
return -1;
|
||||
}
|
||||
|
||||
int minCameraCover(struct TreeNode* root){
|
||||
int ans = 0;
|
||||
|
||||
//在对整个二叉树遍历后。头结点可能未被覆盖,这时候如果函数返回值为0,证明头结点未被覆盖。说明头结点也需要添置摄像头,ans++
|
||||
if(traversal(root, &ans) == 0)
|
||||
ans++;
|
||||
return ans;
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
# 1002. 查找常用字符
|
||||
|
||||
https://leetcode-cn.com/problems/find-common-characters/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/find-common-characters/)
|
||||
|
||||
给定仅有小写字母组成的字符串数组 A,返回列表中的每个字符串中都显示的全部字符(包括重复字符)组成的列表。例如,如果一个字符在每个字符串中出现 3 次,但不是 4 次,则需要在最终答案中包含该字符 3 次。
|
||||
|
||||
@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/find-common-characters/
|
||||
【示例二】
|
||||
输入:["cool","lock","cook"]
|
||||
输出:["c","o"]
|
||||
|
||||
|
||||
|
||||
# 思路
|
||||
|
||||
@ -40,9 +40,9 @@ https://leetcode-cn.com/problems/find-common-characters/
|
||||
|
||||
可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。
|
||||
|
||||
那我们还是哈希法吧。如果对哈希法不了解,可以看这篇:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。
|
||||
那我们还是哈希法吧。如果对哈希法不了解,可以看这篇:[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)。
|
||||
|
||||
如果对用数组来做哈希法不了解的话,可以看这篇:[把数组当做哈希表来用,很巧妙!](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)。
|
||||
如果对用数组来做哈希法不了解的话,可以看这篇:[把数组当做哈希表来用,很巧妙!](https://programmercarl.com/0242.有效的字母异位词.html)。
|
||||
|
||||
了解了哈希法,理解了数组在哈希法中的应用之后,可以来看解题思路了。
|
||||
|
||||
@ -181,7 +181,7 @@ class Solution:
|
||||
# 统计除第一个字符串外字符的出现频率
|
||||
for i in range(1, len(words)):
|
||||
hashOtherStr = [0] * 26
|
||||
for j in range(len(words[0])):
|
||||
for j in range(len(words[i])):
|
||||
hashOtherStr[ord(words[i][j]) - ord('a')] += 1
|
||||
# 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
|
||||
for k in range(26):
|
||||
@ -268,6 +268,47 @@ func min(a,b int)int{
|
||||
return a
|
||||
}
|
||||
```
|
||||
|
||||
Swift:
|
||||
```swift
|
||||
func commonChars(_ words: [String]) -> [String] {
|
||||
var res = [String]()
|
||||
if words.count < 1 {
|
||||
return res
|
||||
}
|
||||
let aUnicodeScalarValue = "a".unicodeScalars.first!.value
|
||||
let lettersMaxCount = 26
|
||||
// 用于统计所有字符串每个字母出现的 最小 频率
|
||||
var hash = Array(repeating: 0, count: lettersMaxCount)
|
||||
// 统计第一个字符串每个字母出现的次数
|
||||
for unicodeScalar in words.first!.unicodeScalars {
|
||||
hash[Int(unicodeScalar.value - aUnicodeScalarValue)] += 1
|
||||
}
|
||||
// 统计除第一个字符串每个字母出现的次数
|
||||
for idx in 1 ..< words.count {
|
||||
var hashOtherStr = Array(repeating: 0, count: lettersMaxCount)
|
||||
for unicodeScalar in words[idx].unicodeScalars {
|
||||
hashOtherStr[Int(unicodeScalar.value - aUnicodeScalarValue)] += 1
|
||||
}
|
||||
// 更新hash,保证hash里统计的字母为出现的最小频率
|
||||
for k in 0 ..< lettersMaxCount {
|
||||
hash[k] = min(hash[k], hashOtherStr[k])
|
||||
}
|
||||
}
|
||||
// 将hash统计的字符次数,转成输出形式
|
||||
for i in 0 ..< lettersMaxCount {
|
||||
while hash[i] != 0 { // 注意这里是while,多个重复的字符
|
||||
let currentUnicodeScalarValue: UInt32 = UInt32(i) + aUnicodeScalarValue
|
||||
let currentUnicodeScalar: UnicodeScalar = UnicodeScalar(currentUnicodeScalarValue)!
|
||||
let outputStr = String(currentUnicodeScalar) // UnicodeScalar -> String
|
||||
res.append(outputStr)
|
||||
hash[i] -= 1
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
```
|
||||
|
||||
-----------------------
|
||||
* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw)
|
||||
* B站视频:[代码随想录](https://space.bilibili.com/525438321)
|
||||
|
@ -110,18 +110,16 @@ class Solution {
|
||||
int len = nums.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
//从前向后遍历,遇到负数将其变为正数,同时K--
|
||||
if (nums[i] < 0 && k > 0) {
|
||||
if (nums[i] < 0 && K > 0) {
|
||||
nums[i] = -nums[i];
|
||||
k--;
|
||||
K--;
|
||||
}
|
||||
}
|
||||
// 如果K还大于0,那么反复转变数值最小的元素,将K用完
|
||||
if (k % 2 == 1) nums[len - 1] = -nums[len - 1];
|
||||
int result = 0;
|
||||
for (int a : nums) {
|
||||
result += a;
|
||||
}
|
||||
return result;
|
||||
|
||||
if (K % 2 == 1) nums[len - 1] = -nums[len - 1];
|
||||
return Arrays.stream(nums).sum();
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
# 1047. 删除字符串中的所有相邻重复项
|
||||
|
||||
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
|
||||
[力扣题目链接](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/)
|
||||
|
||||
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
|
||||
|
||||
@ -26,7 +26,7 @@ https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
|
||||
* 输入:"abbaca"
|
||||
* 输出:"ca"
|
||||
* 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
|
||||
|
||||
|
||||
|
||||
提示:
|
||||
* 1 <= S.length <= 20000
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
## 判断子序列
|
||||
|
||||
[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
|
||||
[动态规划:392.判断子序列](https://programmercarl.com/0392.判断子序列.html) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。
|
||||
|
||||
|
||||
这道题目 其实是可以用双指针或者贪心的的,但是我在开篇的时候就说了这是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。
|
||||
@ -33,9 +33,9 @@ else dp[i][j] = dp[i][j - 1];
|
||||
|
||||
## 不同的子序列
|
||||
|
||||
[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
|
||||
[动态规划:115.不同的子序列](https://programmercarl.com/0115.不同的子序列.html) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。
|
||||
|
||||
本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于[动态规划:392.判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng)就有难度了,这道题目双指针法可就做不了。
|
||||
本题虽然也只有删除操作,不用考虑替换增加之类的,但相对于[动态规划:392.判断子序列](https://programmercarl.com/0392.判断子序列.html)就有难度了,这道题目双指针法可就做不了。
|
||||
|
||||
|
||||
当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。
|
||||
@ -68,9 +68,9 @@ if (s[i - 1] == t[j - 1]) {
|
||||
|
||||
## 两个字符串的删除操作
|
||||
|
||||
[动态规划:583.两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
|
||||
[动态规划:583.两个字符串的删除操作](https://programmercarl.com/0583.两个字符串的删除操作.html)给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。
|
||||
|
||||
本题和[动态规划:115.不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。
|
||||
本题和[动态规划:115.不同的子序列](https://programmercarl.com/0115.不同的子序列.html)相比,其实就是两个字符串可以都可以删除了,情况虽说复杂一些,但整体思路是不变的。
|
||||
|
||||
|
||||
* 当word1[i - 1] 与 word2[j - 1]相同的时候
|
||||
@ -100,10 +100,10 @@ if (word1[i - 1] == word2[j - 1]) {
|
||||
|
||||
## 编辑距离
|
||||
|
||||
[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
|
||||
[动态规划:72.编辑距离](https://programmercarl.com/0072.编辑距离.html) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
|
||||
|
||||
|
||||
编辑距离终于来了,**有了前面三道题目的铺垫,应该有思路了**,本题是两个字符串可以增删改,比 [动态规划:判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng),[动态规划:不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A),[动态规划:两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg)都要复杂的多。
|
||||
编辑距离终于来了,**有了前面三道题目的铺垫,应该有思路了**,本题是两个字符串可以增删改,比 [动态规划:判断子序列](https://programmercarl.com/0392.判断子序列.html),[动态规划:不同的子序列](https://programmercarl.com/0115.不同的子序列.html),[动态规划:两个字符串的删除操作](https://programmercarl.com/0583.两个字符串的删除操作.html)都要复杂的多。
|
||||
|
||||
|
||||
在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:
|
||||
@ -161,7 +161,7 @@ else {
|
||||
|
||||
## 总结
|
||||
|
||||
心思的录友应该会发现我用了三道题做铺垫,才最后引出了[动态规划:72.编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) ,Carl的良苦用心呀,你们体会到了嘛!
|
||||
心思的录友应该会发现我用了三道题做铺垫,才最后引出了[动态规划:72.编辑距离](https://programmercarl.com/0072.编辑距离.html) ,Carl的良苦用心呀,你们体会到了嘛!
|
||||
|
||||
## 其他语言版本
|
||||
|
||||
|
@ -215,7 +215,7 @@ class TreeNode:
|
||||
```
|
||||
|
||||
Go:
|
||||
```
|
||||
```go
|
||||
type TreeNode struct {
|
||||
Val int
|
||||
Left *TreeNode
|
||||
@ -224,7 +224,7 @@ type TreeNode struct {
|
||||
```
|
||||
|
||||
JavaScript:
|
||||
```
|
||||
```javascript
|
||||
function TreeNode(val, left, right) {
|
||||
this.val = (val===undefined ? 0 : val)
|
||||
this.left = (left===undefined ? null : left)
|
||||
|
@ -5,3 +5,7 @@
|
||||
**push代码之前 一定要 先pull最新代码**,否则提交的pr可能会有删除其他录友代码的操作。
|
||||
|
||||
一个pr 不要修改过多文件,因为一旦有一个 文件修改有问题,就不能合入,影响其他文件的合入了。
|
||||
|
||||
git add之前,要git diff 查看一下,本次提交所修改的代码是不是 自己修改的,是否 误删,或者误加的文件。
|
||||
|
||||
提交代码,不要使用git push -f 这种命令,要足够了解 -f 意味着什么。
|
||||
|
@ -112,7 +112,7 @@
|
||||
|
||||
## 总结
|
||||
|
||||
大家如果看了[北京有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/BKrjK4myNB-FYbMqW9f3yw)和[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)就可以看出中国互联网氛围最浓的当然是北京,其次就是上海!
|
||||
大家如果看了[北京有这些互联网公司,你都知道么?](https://programmercarl.com/前序/北京互联网公司总结.html)和[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)就可以看出中国互联网氛围最浓的当然是北京,其次就是上海!
|
||||
|
||||
很多人说深圳才是第二,上海没有产生BAT之类的企业。
|
||||
|
||||
|
@ -10,9 +10,9 @@
|
||||
|
||||
# 空间复杂度分析
|
||||
|
||||
* [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw)
|
||||
* [O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA)
|
||||
* [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)
|
||||
* [关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html)
|
||||
* [O(n)的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html)
|
||||
* [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)
|
||||
|
||||
那么一直还没有讲空间复杂度,所以打算陆续来补上,内容不难,大家可以读一遍文章就有整体的了解了。
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
其实挺简单的,大家看一遍就会了。
|
||||
|
||||
我拿我们刚讲过的这道题[动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA)来做示范。
|
||||
我拿我们刚讲过的这道题[动态规划:使用最小花费爬楼梯](https://programmercarl.com/0746.使用最小花费爬楼梯.html)来做示范。
|
||||
|
||||
力扣746. 使用最小花费爬楼梯,完整的可以在直接本地运行的C++代码如下:
|
||||
|
||||
|
@ -105,7 +105,7 @@
|
||||
|
||||
可能是我写总结写习惯了,什么文章都要有一个总结,哈哈,那么我就总结一下。
|
||||
|
||||
北京的互联网氛围绝对是最好的(暂不讨论户口和房价问题),大家如果看了[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)这篇之后,**会发现北京互联网外企和二线互联网公司数量多的优势,在深圳的互联网公司断档比较严重,如果去不了为数不多的一线公司,可选择的余地就非常少了,而北京选择的余地就很多!**
|
||||
北京的互联网氛围绝对是最好的(暂不讨论户口和房价问题),大家如果看了[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)这篇之后,**会发现北京互联网外企和二线互联网公司数量多的优势,在深圳的互联网公司断档比较严重,如果去不了为数不多的一线公司,可选择的余地就非常少了,而北京选择的余地就很多!**
|
||||
|
||||
相对来说,深圳的硬件企业更多一些,因为珠三角制造业配套比较完善。而大多数互联网公司其实就是媒体公司,当然要靠近政治文化中心,这也是有原因的。
|
||||
|
||||
|
@ -51,7 +51,7 @@
|
||||
|
||||
## 总结
|
||||
|
||||
同在广东省,难免不了要和深圳对比,大家如果看了这篇:[深圳原来有这么多互联网公司,你都知道么?](https://mp.weixin.qq.com/s/3VJHF2zNohBwDBxARFIn-Q)就能感受到鲜明的对比了。
|
||||
同在广东省,难免不了要和深圳对比,大家如果看了这篇:[深圳原来有这么多互联网公司,你都知道么?](https://programmercarl.com/前序/深圳互联网公司总结.html)就能感受到鲜明的对比了。
|
||||
|
||||
广州大厂高端岗位其实比较少,本土只有微信和网易,微信呢毕竟还是腾讯的分部,而网易被很多人认为是杭州企业,其实网易总部在广州。
|
||||
|
||||
|
@ -67,11 +67,10 @@
|
||||
* 大搜车(总部)中国领先的汽车交易服务供应商
|
||||
* 二更(总部)自媒体
|
||||
* 丁香园(总部)
|
||||
|
||||
|
||||
## 总结
|
||||
|
||||
杭州距离上海非常近,难免不了和上海做对比,上海是金融之都,如果看了[上海有这些互联网公司,你都知道么?](https://mp.weixin.qq.com/s/iW4_rXQzc0fJDuSmPTUVdQ)就会发现上海互联网也是仅次于北京的。
|
||||
杭州距离上海非常近,难免不了和上海做对比,上海是金融之都,如果看了[上海有这些互联网公司,你都知道么?](https://programmercarl.com/前序/上海互联网公司总结.html)就会发现上海互联网也是仅次于北京的。
|
||||
|
||||
而杭州是阿里的大本营,到处都有阿里的影子,虽然有网易在,但是也基本是盖过去了,很多中小公司也都是阿里某某高管出来创业的。
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
# 递归算法的时间与空间复杂度分析!
|
||||
|
||||
之前在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中详细讲解了递归算法的时间复杂度,但没有讲空间复杂度。
|
||||
之前在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中详细讲解了递归算法的时间复杂度,但没有讲空间复杂度。
|
||||
|
||||
本篇讲通过求斐波那契数列和二分法再来深入分析一波递归算法的时间和空间复杂度,细心看完,会刷新对递归的认知!
|
||||
|
||||
|
@ -264,6 +264,49 @@ javaScript:
|
||||
};
|
||||
```
|
||||
|
||||
Swift:
|
||||
|
||||
```swift
|
||||
func replaceSpace(_ s: String) -> String {
|
||||
var strArr = Array(s)
|
||||
var count = 0
|
||||
|
||||
// 统计空格的个数
|
||||
for i in strArr {
|
||||
if i == " " {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
// left 指向旧数组的最后一个元素
|
||||
var left = strArr.count - 1
|
||||
// right 指向扩容后数组的最后一个元素(这里还没对数组进行实际上的扩容)
|
||||
var right = strArr.count + count * 2 - 1
|
||||
|
||||
// 实际对数组扩容
|
||||
for _ in 0..<(count * 2) {
|
||||
strArr.append(" ")
|
||||
}
|
||||
|
||||
while left < right {
|
||||
if strArr[left] == " " {
|
||||
strArr[right] = "0"
|
||||
strArr[right - 1] = "2"
|
||||
strArr[right - 2] = "%"
|
||||
left -= 1
|
||||
right -= 3
|
||||
} else {
|
||||
strArr[right] = strArr[left]
|
||||
left -= 1
|
||||
right -= 1
|
||||
}
|
||||
}
|
||||
|
||||
return String(strArr)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
-----------------------
|
||||
|
@ -214,6 +214,34 @@ var reverseLeftWords = function (s, n) {
|
||||
};
|
||||
```
|
||||
|
||||
Swift:
|
||||
|
||||
```swift
|
||||
func reverseLeftWords(_ s: String, _ n: Int) -> String {
|
||||
var ch = Array(s)
|
||||
let len = ch.count
|
||||
// 反转区间[0, n - 1]
|
||||
reverseString(&ch, startIndex: 0, endIndex: n - 1)
|
||||
// 反转区间[n, len - 1]
|
||||
reverseString(&ch, startIndex: n, endIndex: len - 1)
|
||||
// 反转区间[0, len - 1],也就是整个字符串反转
|
||||
reverseString(&ch, startIndex: 0, endIndex: len - 1)
|
||||
return String(ch)
|
||||
}
|
||||
|
||||
func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) {
|
||||
var start = startIndex
|
||||
var end = endIndex
|
||||
while start < end {
|
||||
(s[start], s[end]) = (s[end], s[start])
|
||||
start += 1
|
||||
end -= 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
本周我们开始讲解了二叉树,在[关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/_ymfWYvTNd2GvWvC5HOE4A)中讲解了二叉树的理论基础。
|
||||
本周我们开始讲解了二叉树,在[关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)中讲解了二叉树的理论基础。
|
||||
|
||||
有同学会把红黑树和二叉平衡搜索树弄分开了,其实红黑树就是一种二叉平衡搜索树,这两个树不是独立的,所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树。
|
||||
|
||||
@ -48,9 +48,9 @@ morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以
|
||||
|
||||
## 周二
|
||||
|
||||
在[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA)中讲到了递归三要素,以及前中后序的递归写法。
|
||||
在[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)中讲到了递归三要素,以及前中后序的递归写法。
|
||||
|
||||
文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/PwVIfxDlT3kRgMASWAMGhA),依然可以解决n叉树的前后序遍历,在leetcode上分别是
|
||||
文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html),依然可以解决n叉树的前后序遍历,在leetcode上分别是
|
||||
* 589. N叉树的前序遍历
|
||||
* 590. N叉树的后序遍历
|
||||
|
||||
@ -58,7 +58,7 @@ morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以
|
||||
|
||||
## 周三
|
||||
|
||||
在[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)中我们开始用栈来实现递归的写法,也就是所谓的迭代法。
|
||||
在[二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)中我们开始用栈来实现递归的写法,也就是所谓的迭代法。
|
||||
|
||||
细心的同学发现文中前后序遍历空节点是否入栈写法是不同的
|
||||
|
||||
@ -121,7 +121,7 @@ public:
|
||||
|
||||
## 周四
|
||||
|
||||
在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中我们使用空节点作为标记,给出了统一的前中后序迭代法。
|
||||
在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)中我们使用空节点作为标记,给出了统一的前中后序迭代法。
|
||||
|
||||
此时又多了一种前中后序的迭代写法,那么有同学问了:前中后序迭代法是不是一定要统一来写,这样才算是规范。
|
||||
|
||||
@ -131,7 +131,7 @@ public:
|
||||
|
||||
## 周五
|
||||
|
||||
在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。
|
||||
在[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。
|
||||
|
||||
看完这篇文章,去leetcode上怒刷五题,文章中 编号107题目的样例图放错了(原谅我匆忙之间总是手抖),但不影响大家理解。
|
||||
|
||||
@ -141,7 +141,7 @@ public:
|
||||
|
||||
## 周六
|
||||
|
||||
在[二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获!
|
||||
在[二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获!
|
||||
|
||||
|
||||
**文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。**
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
本周刚开始我们讲解了判断二叉树是否对称的写法, [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。
|
||||
本周刚开始我们讲解了判断二叉树是否对称的写法, [二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)。
|
||||
|
||||
这道题目的本质是要比较两个树(这两个树是根节点的左右子树),遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
* 100.相同的树
|
||||
* 572.另一个树的子树
|
||||
|
||||
**[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中的递归法和迭代法只需要稍作修改其中一个树的遍历顺序,便可刷了100.相同的树。**
|
||||
**[二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)中的递归法和迭代法只需要稍作修改其中一个树的遍历顺序,便可刷了100.相同的树。**
|
||||
|
||||
100.相同的树的递归代码如下:
|
||||
|
||||
@ -102,11 +102,11 @@ public:
|
||||
|
||||
## 周二
|
||||
|
||||
在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,我们讲解了如何求二叉树的最大深度。
|
||||
在[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中,我们讲解了如何求二叉树的最大深度。
|
||||
|
||||
本题可以使用前序,也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序呢求的是高度。
|
||||
|
||||
**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度,所以[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中使用的是后序遍历。
|
||||
**而根节点的高度就是二叉树的最大深度**,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度,所以[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)中使用的是后序遍历。
|
||||
|
||||
本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**)
|
||||
```CPP
|
||||
@ -169,7 +169,7 @@ public:
|
||||
|
||||
## 周三
|
||||
|
||||
在[二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)中,我们讲解如何求二叉树的最小深度, 这道题目要是稍不留心很容易犯错。
|
||||
在[二叉树:看看这些树的最小深度](https://programmercarl.com/0111.二叉树的最小深度.html)中,我们讲解如何求二叉树的最小深度, 这道题目要是稍不留心很容易犯错。
|
||||
|
||||
**注意这里最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。**
|
||||
|
||||
@ -177,19 +177,19 @@ public:
|
||||
|
||||
**求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。**
|
||||
|
||||
注意到这一点之后 递归法和迭代法 都可以参照[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)写出来。
|
||||
注意到这一点之后 递归法和迭代法 都可以参照[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html)写出来。
|
||||
|
||||
## 周四
|
||||
|
||||
我们在[二叉树:我有多少个节点?](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw)中,讲解了如何求二叉树的节点数量。
|
||||
我们在[二叉树:我有多少个节点?](https://programmercarl.com/0222.完全二叉树的节点个数.html)中,讲解了如何求二叉树的节点数量。
|
||||
|
||||
这一天是十一长假的第一天,又是双节,所以简单一些,只要把之前两篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg), [二叉树:看看这些树的最小深度](https://mp.weixin.qq.com/s/BH8-gPC3_QlqICDg7rGSGA)都认真看了的话,这道题目可以分分钟刷掉了。
|
||||
这一天是十一长假的第一天,又是双节,所以简单一些,只要把之前两篇[二叉树:看看这些树的最大深度](https://programmercarl.com/0104.二叉树的最大深度.html), [二叉树:看看这些树的最小深度](https://programmercarl.com/0111.二叉树的最小深度.html)都认真看了的话,这道题目可以分分钟刷掉了。
|
||||
|
||||
估计此时大家对这一类求二叉树节点数量以及求深度应该非常熟练了。
|
||||
|
||||
## 周五
|
||||
|
||||
在[二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww)中讲解了如何判断二叉树是否是平衡二叉树
|
||||
在[二叉树:我平衡么?](https://programmercarl.com/0110.平衡二叉树.html)中讲解了如何判断二叉树是否是平衡二叉树
|
||||
|
||||
今天讲解一道判断平衡二叉树的题目,其实 方法上我们之前讲解深度的时候都讲过了,但是这次我们通过这道题目彻底搞清楚二叉树高度与深度的问题,以及对应的遍历方式。
|
||||
|
||||
@ -212,7 +212,7 @@ public:
|
||||
|
||||
## 周六
|
||||
|
||||
在[二叉树:找我的所有路径?](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA)中正式涉及到了回溯,很多同学过了这道题目,可能都不知道自己使用了回溯,其实回溯和递归都是相伴相生的。最后我依然给出了迭代法的版本。
|
||||
在[二叉树:找我的所有路径?](https://programmercarl.com/0257.二叉树的所有路径.html)中正式涉及到了回溯,很多同学过了这道题目,可能都不知道自己使用了回溯,其实回溯和递归都是相伴相生的。最后我依然给出了迭代法的版本。
|
||||
|
||||
我在题解中第一个版本的代码会把回溯的过程充分体现出来,如果大家直接看简洁的代码版本,很可能就会忽略的回溯的存在。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[二叉树:以为使用了递归,其实还隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)中,通过leetcode [257.二叉树的所有路径这道题目](https://mp.weixin.qq.com/s/Osw4LQD2xVUnCJ-9jrYxJA),讲解了递归如何隐藏着回溯,一些代码会把回溯的过程都隐藏了起来了,甚至刷过这道题的同学可能都不知道自己用了回溯。
|
||||
在[二叉树:以为使用了递归,其实还隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)中,通过leetcode [257.二叉树的所有路径这道题目](https://programmercarl.com/0257.二叉树的所有路径.html),讲解了递归如何隐藏着回溯,一些代码会把回溯的过程都隐藏了起来了,甚至刷过这道题的同学可能都不知道自己用了回溯。
|
||||
|
||||
文章中第一版代码把每一个细节都展示了输出来了,大家可以清晰的看到回溯的过程。
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
|
||||
## 周二
|
||||
|
||||
在文章[二叉树:做了这么多题目了,我的左叶子之和是多少?](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA) 中提供了另一个判断节点属性的思路,平时我们习惯了使用通过节点的左右孩子判断本节点的属性,但发现使用这个思路无法判断左叶子。
|
||||
在文章[二叉树:做了这么多题目了,我的左叶子之和是多少?](https://programmercarl.com/0404.左叶子之和.html) 中提供了另一个判断节点属性的思路,平时我们习惯了使用通过节点的左右孩子判断本节点的属性,但发现使用这个思路无法判断左叶子。
|
||||
|
||||
此时需要相连的三层之间构成的约束条件,也就是要通过节点的父节点以及孩子节点来判断本节点的属性。
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
## 周三
|
||||
|
||||
在[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中的题目如果使用递归的写法还是有点难度的,层次遍历反而很简单。
|
||||
在[二叉树:我的左下角的值是多少?](https://programmercarl.com/0513.找树左下角的值.html)中的题目如果使用递归的写法还是有点难度的,层次遍历反而很简单。
|
||||
|
||||
题目其实就是要在树的**最后一行**找到**最左边的值**。
|
||||
|
||||
@ -32,26 +32,26 @@
|
||||
|
||||
在这篇文章中,我们使用递归算法实实在在的求了一次深度,然后使用靠左的遍历,保证求得靠左的最大深度,而且又一次使用了回溯。
|
||||
|
||||
如果对二叉树的高度与深度又有点模糊了,在看这里[二叉树:我平衡么?](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww),回忆一下吧。
|
||||
如果对二叉树的高度与深度又有点模糊了,在看这里[二叉树:我平衡么?](https://programmercarl.com/0110.平衡二叉树.html),回忆一下吧。
|
||||
|
||||
[二叉树:我的左下角的值是多少?](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)中把我们之前讲过的内容都过了一遍,此外,还用前序遍历的技巧求得了靠左的最大深度。
|
||||
[二叉树:我的左下角的值是多少?](https://programmercarl.com/0513.找树左下角的值.html)中把我们之前讲过的内容都过了一遍,此外,还用前序遍历的技巧求得了靠左的最大深度。
|
||||
|
||||
**求二叉树的各种最值,就想应该采用什么样的遍历顺序,确定了遍历循序,其实就和数组求最值一样容易了。**
|
||||
|
||||
|
||||
## 周四
|
||||
|
||||
在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)中通过两道题目,彻底说清楚递归函数的返回值问题。
|
||||
在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)中通过两道题目,彻底说清楚递归函数的返回值问题。
|
||||
|
||||
一般情况下:**如果需要搜索整颗二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回。**
|
||||
|
||||
特别是有些时候 递归函数的返回值是bool类型,一些同学会疑惑为啥要加这个,其实就是为了找到一条边立刻返回。
|
||||
|
||||
其实还有一种就是后序遍历需要根据左右递归的返回值推出中间节点的状态,这种需要有返回值,例如[222.完全二叉树](https://mp.weixin.qq.com/s/2_eAjzw-D0va9y4RJgSmXw),[110.平衡二叉树](https://mp.weixin.qq.com/s/isUS-0HDYknmC0Rr4R8mww),这几道我们之前也讲过。
|
||||
其实还有一种就是后序遍历需要根据左右递归的返回值推出中间节点的状态,这种需要有返回值,例如[222.完全二叉树](https://programmercarl.com/0222.完全二叉树的节点个数.html),[110.平衡二叉树](https://programmercarl.com/0110.平衡二叉树.html),这几道我们之前也讲过。
|
||||
|
||||
## 周五
|
||||
|
||||
之前都是讲解遍历二叉树,这次该构造二叉树了,在[二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)中,我们通过前序和中序,后序和中序,构造了唯一的一颗二叉树。
|
||||
之前都是讲解遍历二叉树,这次该构造二叉树了,在[二叉树:构造二叉树登场!](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)中,我们通过前序和中序,后序和中序,构造了唯一的一颗二叉树。
|
||||
|
||||
**构造二叉树有三个注意的点:**
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
|
||||
## 周六
|
||||
|
||||
知道了如何构造二叉树,那么使用一个套路就可以解决文章[二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)中的问题。
|
||||
知道了如何构造二叉树,那么使用一个套路就可以解决文章[二叉树:构造一棵最大的二叉树](https://programmercarl.com/0654.最大二叉树.html)中的问题。
|
||||
|
||||
**注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下表索引直接在原数组上操作,这样可以节约时间和空间上的开销。**
|
||||
|
||||
@ -77,12 +77,12 @@
|
||||
|
||||
本周我们深度讲解了如下知识点:
|
||||
|
||||
1. [递归中如何隐藏着回溯](https://mp.weixin.qq.com/s/ivLkHzWdhjQQD1rQWe6zWA)
|
||||
2. [如何通过三层关系确定左叶子](https://mp.weixin.qq.com/s/gBAgmmFielojU5Wx3wqFTA)
|
||||
3. [如何通过二叉树深度来判断左下角的值](https://mp.weixin.qq.com/s/MH2gbLvzQ91jHPKqiub0Nw)
|
||||
4. [递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg)
|
||||
5. [前序和中序,后序和中序构造唯一二叉树](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg)
|
||||
6. [使用数组构造某一特性的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w)
|
||||
1. [递归中如何隐藏着回溯](https://programmercarl.com/二叉树中递归带着回溯.html)
|
||||
2. [如何通过三层关系确定左叶子](https://programmercarl.com/0404.左叶子之和.html)
|
||||
3. [如何通过二叉树深度来判断左下角的值](https://programmercarl.com/0513.找树左下角的值.html)
|
||||
4. [递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)
|
||||
5. [前序和中序,后序和中序构造唯一二叉树](https://programmercarl.com/0106.从中序与后序遍历序列构造二叉树.html)
|
||||
6. [使用数组构造某一特性的二叉树](https://programmercarl.com/0654.最大二叉树.html)
|
||||
|
||||
**如果大家一路跟下来,一定收获满满,如果周末不做这个总结,大家可能都不知道自己收获满满,啊哈!**
|
||||
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ)中讲解了如何合并两个二叉树,平时我们都习惯了操作一个二叉树,一起操作两个树可能还有点陌生。
|
||||
在[二叉树:合并两个二叉树](https://programmercarl.com/0617.合并二叉树.html)中讲解了如何合并两个二叉树,平时我们都习惯了操作一个二叉树,一起操作两个树可能还有点陌生。
|
||||
|
||||
其实套路是一样,只不过一起操作两个树的指针,我们之前讲过求 [二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)的时候,已经初步涉及到了 一起遍历两颗二叉树了。
|
||||
其实套路是一样,只不过一起操作两个树的指针,我们之前讲过求 [二叉树:我对称么?](https://programmercarl.com/0101.对称二叉树.html)的时候,已经初步涉及到了 一起遍历两颗二叉树了。
|
||||
|
||||
**迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。**
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
周二开始讲解一个新的树,二叉搜索树,开始要换一个思路了,如果没有利用好二叉搜索树的特性,就容易把简单题做成了难题了。
|
||||
|
||||
学习[二叉搜索树的特性](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),还是比较容易的。
|
||||
学习[二叉搜索树的特性](https://programmercarl.com/0700.二叉搜索树中的搜索.html),还是比较容易的。
|
||||
|
||||
大多是二叉搜索树的题目,其实都离不开中序遍历,因为这样就是有序的。
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
## 周三
|
||||
|
||||
了解了二搜索树的特性之后, 开始验证[一颗二叉树是不是二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)。
|
||||
了解了二搜索树的特性之后, 开始验证[一颗二叉树是不是二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)。
|
||||
|
||||
首先在此强调一下二叉搜索树的特性:
|
||||
|
||||
@ -46,15 +46,15 @@
|
||||
|
||||
**在二叉树中通过两个前后指针作比较,会经常用到**。
|
||||
|
||||
本文[二叉树:我是不是一棵二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)中迭代法中为什么没有周一那篇那么简洁了呢,因为本篇是验证二叉搜索树,前提默认它是一棵普通二叉树,所以还是要回归之前老办法。
|
||||
本文[二叉树:我是不是一棵二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)中迭代法中为什么没有周一那篇那么简洁了呢,因为本篇是验证二叉搜索树,前提默认它是一棵普通二叉树,所以还是要回归之前老办法。
|
||||
|
||||
## 周四
|
||||
|
||||
了解了[二叉搜索树](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),并且知道[如何判断二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q),本篇就很简单了。
|
||||
了解了[二叉搜索树](https://programmercarl.com/0700.二叉搜索树中的搜索.html),并且知道[如何判断二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html),本篇就很简单了。
|
||||
|
||||
**要知道二叉搜索树和中序遍历是好朋友!**
|
||||
|
||||
在[二叉树:搜索树的最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ)中强调了要利用搜索树的特性,把这道题目想象成在一个有序数组上求两个数最小差值,这就是一道送分题了。
|
||||
在[二叉树:搜索树的最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html)中强调了要利用搜索树的特性,把这道题目想象成在一个有序数组上求两个数最小差值,这就是一道送分题了。
|
||||
|
||||
**需要明确:在有序数组求任意两数最小值差等价于相邻两数的最小值差**。
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
|
||||
此时大家应该知道遇到二叉搜索树,就想是有序数组,那么在二叉搜索树中求二叉搜索树众数就很简单了。
|
||||
|
||||
在[二叉树:我的众数是多少?](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)中我给出了如果是普通二叉树,应该如何求众数的集合,然后进一步讲解了二叉搜索树应该如何求众数集合。
|
||||
在[二叉树:我的众数是多少?](https://programmercarl.com/0501.二叉搜索树中的众数.html)中我给出了如果是普通二叉树,应该如何求众数的集合,然后进一步讲解了二叉搜索树应该如何求众数集合。
|
||||
|
||||
在求众数集合的时候有一个技巧,因为题目中众数是可以有多个的,所以一般的方法需要遍历两遍才能求出众数的集合。
|
||||
|
||||
@ -74,7 +74,7 @@
|
||||
|
||||
## 周六
|
||||
|
||||
在[二叉树:公共祖先问题](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)中,我们开始讲解如何在二叉树中求公共祖先的问题,本来是打算和二叉搜索树一起讲的,但发现篇幅过长,所以先讲二叉树的公共祖先问题。
|
||||
在[二叉树:公共祖先问题](https://programmercarl.com/0236.二叉树的最近公共祖先.html)中,我们开始讲解如何在二叉树中求公共祖先的问题,本来是打算和二叉搜索树一起讲的,但发现篇幅过长,所以先讲二叉树的公共祖先问题。
|
||||
|
||||
**如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。**
|
||||
|
||||
@ -92,13 +92,13 @@
|
||||
|
||||
## 总结
|
||||
|
||||
本周我们讲了[如何合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ),了解了如何操作两个二叉树。
|
||||
本周我们讲了[如何合并两个二叉树](https://programmercarl.com/0617.合并二叉树.html),了解了如何操作两个二叉树。
|
||||
|
||||
然后开始另一种树:二叉搜索树,了解[二叉搜索树的特性](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg),然后[判断一棵二叉树是不是二叉搜索树](https://mp.weixin.qq.com/s/8odY9iUX5eSi0eRFSXFD4Q)。
|
||||
然后开始另一种树:二叉搜索树,了解[二叉搜索树的特性](https://programmercarl.com/0700.二叉搜索树中的搜索.html),然后[判断一棵二叉树是不是二叉搜索树](https://programmercarl.com/0098.验证二叉搜索树.html)。
|
||||
|
||||
了解以上知识之后,就开始利用其特性,做一些二叉搜索树上的题目,[求最小绝对差](https://mp.weixin.qq.com/s/Hwzml6698uP3qQCC1ctUQQ),[求众数集合](https://mp.weixin.qq.com/s/KSAr6OVQIMC-uZ8MEAnGHg)。
|
||||
了解以上知识之后,就开始利用其特性,做一些二叉搜索树上的题目,[求最小绝对差](https://programmercarl.com/0530.二叉搜索树的最小绝对差.html),[求众数集合](https://programmercarl.com/0501.二叉搜索树中的众数.html)。
|
||||
|
||||
接下来,开始求二叉树与二叉搜索树的公共祖先问题,单篇篇幅原因,先单独介绍[普通二叉树如何求最近公共祖先](https://mp.weixin.qq.com/s/n6Rk3nc_X3TSkhXHrVmBTQ)。
|
||||
接下来,开始求二叉树与二叉搜索树的公共祖先问题,单篇篇幅原因,先单独介绍[普通二叉树如何求最近公共祖先](https://programmercarl.com/0236.二叉树的最近公共祖先.html)。
|
||||
|
||||
现在已经讲过了几种二叉树了,二叉树,二叉平衡树,完全二叉树,二叉搜索树,后面还会有平衡二叉搜索树。 那么一些同学难免会有混乱了,我针对如下三个问题,帮大家在捋顺一遍:
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
本周我们正式开始了回溯算法系列,那么首先当然是概述。
|
||||
|
||||
在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中介绍了什么是回溯,回溯法的效率,回溯法解决的问题以及回溯法模板。
|
||||
在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中介绍了什么是回溯,回溯法的效率,回溯法解决的问题以及回溯法模板。
|
||||
|
||||
**回溯是递归的副产品,只要有递归就会有回溯**。
|
||||
|
||||
@ -31,14 +31,14 @@
|
||||
|
||||
回溯法确实不好理解,所以需要把回溯法抽象为一个图形来理解就容易多了,每一道回溯法的题目都可以抽象为树形结构。
|
||||
|
||||
针对很多同学都写不好回溯,我在[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)用回溯三部曲,分析了回溯算法,并给出了回溯法的模板。
|
||||
针对很多同学都写不好回溯,我在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)用回溯三部曲,分析了回溯算法,并给出了回溯法的模板。
|
||||
|
||||
这个模板会伴随整个回溯法系列!
|
||||
|
||||
## 周二
|
||||
|
||||
|
||||
在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们开始用回溯法解决第一道题目,组合问题。
|
||||
在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目,组合问题。
|
||||
|
||||
我在文中开始的时候给大家列举k层for循环例子,进而得出都是同样是暴利解法,为什么要用回溯法。
|
||||
|
||||
@ -48,19 +48,19 @@
|
||||
|
||||
## 周三
|
||||
|
||||
针对[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)还可以做剪枝的操作。
|
||||
针对[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)还可以做剪枝的操作。
|
||||
|
||||
在[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中把回溯法代码做了剪枝优化,在文中我依然把问题抽象为一个树形结构,大家可以一目了然剪的究竟是哪里。
|
||||
在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,在文中我依然把问题抽象为一个树形结构,大家可以一目了然剪的究竟是哪里。
|
||||
|
||||
**剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够 题目要求的k个元素了,就没有必要搜索了**。
|
||||
|
||||
## 周四
|
||||
|
||||
在[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中,相当于 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)加了一个元素总和的限制。
|
||||
在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。
|
||||
|
||||
整体思路还是一样的,本题的剪枝会好想一些,即:**已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉**。
|
||||
|
||||
在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA)中提到的,对for循环选择的起始范围的剪枝。
|
||||
在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的,对for循环选择的起始范围的剪枝。
|
||||
|
||||
所以,剪枝的代码,可以把for循环,加上 `i <= 9 - (k - path.size()) + 1` 的限制!
|
||||
|
||||
@ -68,11 +68,11 @@
|
||||
|
||||
## 周五
|
||||
|
||||
在[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。
|
||||
在[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)中,开始用多个集合来求组合,还是熟悉的模板题目,但是有一些细节。
|
||||
|
||||
例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)中从startIndex开始遍历的。
|
||||
例如这里for循环,可不像是在 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中从startIndex开始遍历的。
|
||||
|
||||
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)和[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)都是是求同一个集合中的组合!**
|
||||
**因为本题每一个数字代表的是不同集合,也就是求不同集合之间的组合,而[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)和[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)都是是求同一个集合中的组合!**
|
||||
|
||||
如果大家在现场面试的时候,一定要注意各种输入异常的情况,例如本题输入1 * #按键。
|
||||
|
||||
@ -82,9 +82,9 @@
|
||||
|
||||
因为之前链表系列没有写总结,虽然链表系列已经是两个月前的事情,但还是有必要补一下。
|
||||
|
||||
所以给出[链表:总结篇!](https://mp.weixin.qq.com/s/vK0JjSTHfpAbs8evz5hH8A),这里对之前链表理论基础和经典题目进行了总结。
|
||||
所以给出[链表:总结篇!](https://programmercarl.com/链表总结篇.html),这里对之前链表理论基础和经典题目进行了总结。
|
||||
|
||||
同时对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中求环入口的问题又进行了补充证明,可以说把环形链表的方方面面都讲的很通透了,大家如果没有做过环形链表的题目一定要去做一做。
|
||||
同时对[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中求环入口的问题又进行了补充证明,可以说把环形链表的方方面面都讲的很通透了,大家如果没有做过环形链表的题目一定要去做一做。
|
||||
|
||||
## 总结
|
||||
|
||||
|
@ -6,17 +6,17 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)中讲解的组合总和问题,和以前的组合问题还都不一样。
|
||||
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)中讲解的组合总和问题,和以前的组合问题还都不一样。
|
||||
|
||||
本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
|
||||
本题和[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
|
||||
|
||||
不少录友都是看到可以重复选择,就义无反顾的把startIndex去掉了。
|
||||
|
||||
**本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?**
|
||||
|
||||
我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[回溯算法:求组合总和!](https://mp.weixin.qq.com/s/HX7WW6ixbFZJASkRnCTC3w)。
|
||||
我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html),[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)。
|
||||
|
||||
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://mp.weixin.qq.com/s/e2ua2cmkE_vpYjM3j6HY0A)
|
||||
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:[回溯算法:电话号码的字母组合](https://programmercarl.com/0017.电话号码的字母组合.html)
|
||||
|
||||
**注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍**。
|
||||
|
||||
@ -30,13 +30,13 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
**在求和问题中,排序之后加剪枝是常见的套路!**
|
||||
|
||||
在[回溯算法:求组合总和(二)](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)第一个树形结构没有画出startIndex的作用,**这里这里纠正一下,准确的树形结构如图所示:**
|
||||
在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)第一个树形结构没有画出startIndex的作用,**这里这里纠正一下,准确的树形结构如图所示:**
|
||||
|
||||

|
||||
|
||||
## 周二
|
||||
|
||||
在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。
|
||||
在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。
|
||||
|
||||
**所以难就难在去重问题上了**。
|
||||
|
||||
@ -60,7 +60,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
## 周三
|
||||
|
||||
在[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。
|
||||
在[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。
|
||||
|
||||
我列出如下几个难点:
|
||||
|
||||
@ -85,9 +85,9 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target;
|
||||
|
||||
## 周四
|
||||
|
||||
如果没有做过[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)的话,[回溯算法:复原IP地址](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)这道题目应该是比较难的。
|
||||
如果没有做过[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)的话,[回溯算法:复原IP地址](https://programmercarl.com/0093.复原IP地址.html)这道题目应该是比较难的。
|
||||
|
||||
复原IP照[回溯算法:分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。
|
||||
复原IP照[回溯算法:分割回文串](https://programmercarl.com/0131.分割回文串.html)就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。
|
||||
|
||||
树形图如下:
|
||||
|
||||
@ -109,7 +109,7 @@ if (s.size() > 12) return result; // 剪枝
|
||||
|
||||
## 周五
|
||||
|
||||
在[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。
|
||||
在[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)中讲解了子集问题,**在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果**。
|
||||
|
||||
如图:
|
||||
|
||||
@ -135,13 +135,13 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
|
||||
|
||||
## 周六
|
||||
|
||||
早起的哈希表系列没有总结,所以[哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg)如约而至。
|
||||
早起的哈希表系列没有总结,所以[哈希表:总结篇!(每逢总结必经典)](https://programmercarl.com/哈希表总结.html)如约而至。
|
||||
|
||||
可能之前大家做过很多哈希表的题目,但是没有串成线,总结篇来帮你串成线,捋顺哈希表的整个脉络。
|
||||
|
||||
大家对什么时候各种set与map比较疑惑,想深入了解红黑树,哈希之类的。
|
||||
|
||||
**如果真的只是想清楚什么时候使用各种set与map,不用看那么多,把[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)看了就够了**。
|
||||
**如果真的只是想清楚什么时候使用各种set与map,不用看那么多,把[关于哈希表,你该了解这些!](https://programmercarl.com/哈希表理论基础.html)看了就够了**。
|
||||
|
||||
## 总结
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)中,开始针对子集问题进行去重。
|
||||
在[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)中,开始针对子集问题进行去重。
|
||||
|
||||
本题就是[回溯算法:求子集问题!](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://mp.weixin.qq.com/s/_1zPYk70NvHsdY8UWVGXmQ)也讲过了。
|
||||
本题就是[回溯算法:求子集问题!](https://programmercarl.com/0078.子集.html)的基础上加上了去重,去重我们在[回溯算法:求组合总和(三)](https://programmercarl.com/0040.组合总和II.html)也讲过了。
|
||||
|
||||
所以本题对大家应该并不难。
|
||||
|
||||
@ -16,18 +16,18 @@
|
||||
|
||||
## 周二
|
||||
|
||||
在[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨!
|
||||
在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨!
|
||||
|
||||
树形结构如下:
|
||||

|
||||
|
||||
[回溯算法:递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ)留言区大家有很多疑问,主要还是和[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)混合在了一起。
|
||||
[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)留言区大家有很多疑问,主要还是和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混合在了一起。
|
||||
|
||||
详细在[本周小结!(回溯算法系列三)续集](https://mp.weixin.qq.com/s/kSMGHc_YpsqL2j-jb_E_Ag)中给出了介绍!
|
||||
|
||||
## 周三
|
||||
|
||||
我们已经分析了组合问题,分割问题,子集问题,那么[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw) 又不一样了。
|
||||
我们已经分析了组合问题,分割问题,子集问题,那么[回溯算法:排列问题!](https://programmercarl.com/0046.全排列.html) 又不一样了。
|
||||
|
||||
排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
## 周四
|
||||
|
||||
排列问题也要去重了,在[回溯算法:排列问题(二)](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)中又一次强调了“树层去重”和“树枝去重”。
|
||||
排列问题也要去重了,在[回溯算法:排列问题(二)](https://programmercarl.com/0047.全排列II.html)中又一次强调了“树层去重”和“树枝去重”。
|
||||
|
||||
树形结构如下:
|
||||
|
||||
@ -87,9 +87,9 @@
|
||||
|
||||
## 总结
|
||||
|
||||
本周我们对[子集问题进行了去重](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ),然后介绍了和子集问题非常像的[递增子序列](https://mp.weixin.qq.com/s/ePxOtX1ATRYJb2Jq7urzHQ),如果还保持惯性思维,这道题就可以掉坑里。
|
||||
本周我们对[子集问题进行了去重](https://programmercarl.com/0090.子集II.html),然后介绍了和子集问题非常像的[递增子序列](https://programmercarl.com/0491.递增子序列.html),如果还保持惯性思维,这道题就可以掉坑里。
|
||||
|
||||
接着介绍了[排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw),以及对[排列问题如何进行去重](https://mp.weixin.qq.com/s/9L8h3WqRP_h8LLWNT34YlA)。
|
||||
接着介绍了[排列问题!](https://programmercarl.com/0046.全排列.html),以及对[排列问题如何进行去重](https://programmercarl.com/0047.全排列II.html)。
|
||||
|
||||
最后我补充了子集问题,排列问题和组合问题的性能分析,给大家提供了回溯算法复杂度的分析思路。
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
本周正式开始了贪心算法,在[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中,我们介绍了什么是贪心以及贪心的套路。
|
||||
本周正式开始了贪心算法,在[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中,我们介绍了什么是贪心以及贪心的套路。
|
||||
|
||||
**贪心的本质是选择每一阶段的局部最优,从而达到全局最优。**
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
## 周二
|
||||
|
||||
|
||||
在[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)中讲解了贪心算法的第一道题目。
|
||||
在[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)中讲解了贪心算法的第一道题目。
|
||||
|
||||
这道题目很明显能看出来是用贪心,也是入门好题。
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
|
||||
所有还是小饼干优先先喂饱小胃口更好一些,也比较直观。
|
||||
|
||||
一些录友不清楚[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)中时间复杂度是怎么来的?
|
||||
一些录友不清楚[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)中时间复杂度是怎么来的?
|
||||
|
||||
就是快排O(nlogn),遍历O(n),加一起就是还是O(nlogn)。
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
接下来就要上一点难度了,要不然大家会误以为贪心算法就是常识判断一下就行了。
|
||||
|
||||
在[贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)中,需要计算最长摇摆序列。
|
||||
在[贪心算法:摆动序列](https://programmercarl.com/0376.摆动序列.html)中,需要计算最长摇摆序列。
|
||||
|
||||
其实就是让序列有尽可能多的局部峰值。
|
||||
|
||||
@ -62,13 +62,13 @@
|
||||
|
||||
## 周四
|
||||
|
||||
在[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)中,详细讲解了用贪心的方式来求最大子序列和,其实这道题目是一道动态规划的题目。
|
||||
在[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)中,详细讲解了用贪心的方式来求最大子序列和,其实这道题目是一道动态规划的题目。
|
||||
|
||||
**贪心的思路为局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。从而推出全局最优:选取最大“连续和”**
|
||||
|
||||
代码很简单,但是思路却比较难。还需要反复琢磨。
|
||||
|
||||
针对[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg)文章中给出的贪心代码如下;
|
||||
针对[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html)文章中给出的贪心代码如下;
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -95,17 +95,17 @@ public:
|
||||
|
||||
## 总结
|
||||
|
||||
本周我们讲解了[贪心算法的理论基础](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),了解了贪心本质:局部最优推出全局最优。
|
||||
本周我们讲解了[贪心算法的理论基础](https://programmercarl.com/贪心算法理论基础.html),了解了贪心本质:局部最优推出全局最优。
|
||||
|
||||
然后讲解了第一道题目[分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw),还是比较基础的,可能会给大家一种贪心算法比较简单的错觉,因为贪心有时候接近于常识。
|
||||
然后讲解了第一道题目[分发饼干](https://programmercarl.com/0455.分发饼干.html),还是比较基础的,可能会给大家一种贪心算法比较简单的错觉,因为贪心有时候接近于常识。
|
||||
|
||||
其实我还准备一些简单的贪心题目,甚至网上很多都质疑这些题目是不是贪心算法。这些题目我没有立刻发出来,因为真的会让大家感觉贪心过于简单,而忽略了贪心的本质:局部最优和全局最优两个关键点。
|
||||
|
||||
**所以我在贪心系列难度会有所交替,难的题目在于拓展思路,简单的题目在于分析清楚其贪心的本质,后续我还会发一些简单的题目来做贪心的分析。**
|
||||
|
||||
在[摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)中大家就初步感受到贪心没那么简单了。
|
||||
在[摆动序列](https://programmercarl.com/0376.摆动序列.html)中大家就初步感受到贪心没那么简单了。
|
||||
|
||||
本周最后是[最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg),这道题目要用贪心的方式做出来,就比较有难度,都知道负数加上正数之后会变小,但是这道题目依然会让很多人搞混淆,其关键在于:**不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数**。这块真的需要仔细体会!
|
||||
本周最后是[最大子序和](https://programmercarl.com/0053.最大子序和.html),这道题目要用贪心的方式做出来,就比较有难度,都知道负数加上正数之后会变小,但是这道题目依然会让很多人搞混淆,其关键在于:**不能让“连续和”为负数的时候加上下一个元素,而不是 不让“连续和”加上一个负数**。这块真的需要仔细体会!
|
||||
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
一说到股票问题,一般都会想到动态规划,其实有时候贪心更有效!
|
||||
|
||||
在[贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)中,讲到只能多次买卖一支股票,如何获取最大利润。
|
||||
在[贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)中,讲到只能多次买卖一支股票,如何获取最大利润。
|
||||
|
||||
**这道题目理解利润拆分是关键点!** 不要整块的去看,而是把整体利润拆为每天的利润,就很容易想到贪心了。
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
## 周二
|
||||
|
||||
在[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)中是给你一个数组看能否跳到终点。
|
||||
在[贪心算法:跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)中是给你一个数组看能否跳到终点。
|
||||
|
||||
本题贪心的关键是:**不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的**。
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
## 周三
|
||||
|
||||
这道题目:[贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)可就有点难了。
|
||||
这道题目:[贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html)可就有点难了。
|
||||
|
||||
本题解题关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**。
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
|
||||
注意:**图中的移动下标是到当前这步覆盖的最远距离(下标2的位置),此时没有到终点,只能增加第二步来扩大覆盖范围**。
|
||||
|
||||
在[贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)中我给出了两个版本的代码。
|
||||
在[贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html)中我给出了两个版本的代码。
|
||||
|
||||
其实本质都是超过当前覆盖范围,步数就加一,但版本一需要考虑当前覆盖最远距离下标是不是数组终点的情况。
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
|
||||
## 周四
|
||||
|
||||
这道题目:[贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)就比较简单了,哈哈,用简单题来讲一讲贪心的思想。
|
||||
这道题目:[贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)就比较简单了,哈哈,用简单题来讲一讲贪心的思想。
|
||||
|
||||
**这里其实用了两次贪心!**
|
||||
|
||||
@ -77,8 +77,7 @@
|
||||
|
||||
第二次贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
|
||||
|
||||
|
||||
[贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)中的代码,最后while处理K的时候,其实直接判断奇偶数就可以了,文中给出的方式太粗暴了,哈哈,Carl大意了。
|
||||
[贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)中的代码,最后while处理K的时候,其实直接判断奇偶数就可以了,文中给出的方式太粗暴了,哈哈,Carl大意了。
|
||||
|
||||
例外一位录友留言给出一个很好的建议,因为文中是使用快排,仔细看题,**题目中限定了数据范围是正负一百,所以可以使用桶排序**,这样时间复杂度就可以优化为O(n)了。但可能代码要复杂一些了。
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
# 周一
|
||||
|
||||
在[程序员的简历应该这么写!!(附简历模板)](https://mp.weixin.qq.com/s/nCTUzuRTBo1_R_xagVszsA)中以我自己的总结经验为例讲一讲大家应该如何写简历。
|
||||
在[程序员的简历应该这么写!!(附简历模板)](https://programmercarl.com/前序/程序员简历.html)中以我自己的总结经验为例讲一讲大家应该如何写简历。
|
||||
|
||||
主要有如下几点:
|
||||
|
||||
@ -49,7 +49,7 @@
|
||||
|
||||
# 周二
|
||||
|
||||
在[关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw)中详细讲解了时间复杂度,很多被大家忽略的内容,在文中都做了详细的解释。
|
||||
在[关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html)中详细讲解了时间复杂度,很多被大家忽略的内容,在文中都做了详细的解释。
|
||||
|
||||
文中涉及如下问题:
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
这些问题大家可能懵懵懂懂的了解一些,但一细问又答不上来。
|
||||
|
||||
相信看完本篇[关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw),以上问题大家就理解的清晰多了。
|
||||
相信看完本篇[关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html),以上问题大家就理解的清晰多了。
|
||||
|
||||
文中最后还运用以上知识通过一道简单的题目具体分析了一下其时间复杂度,给出两种方法究竟谁最优。
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
|
||||
# 周三
|
||||
|
||||
在[O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时!
|
||||
在[O(n)的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时!
|
||||
|
||||
估计很多录友知道算法超时了,但没有注意过 O(n)的算法,如果1s内出结果,这个n究竟是多大?
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
|
||||
# 周四
|
||||
|
||||
在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中,讲一讲如果计算递归算法的时间复杂度。
|
||||
在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中,讲一讲如果计算递归算法的时间复杂度。
|
||||
|
||||
递归的时间复杂度等于**递归的次数 * 每次递归中的操作次数**。
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)中给出每一个加油站的汽油和开到这个加油站的消耗,问汽车能不能开一圈。
|
||||
在[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)中给出每一个加油站的汽油和开到这个加油站的消耗,问汽车能不能开一圈。
|
||||
|
||||
这道题目咋眼一看,感觉是一道模拟题,模拟一下汽车从每一个节点出发看看能不能开一圈,时间复杂度是O(n^2)。
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
## 周二
|
||||
|
||||
在[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)中我们第一次接触了需要考虑两个维度的情况。
|
||||
在[贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html)中我们第一次接触了需要考虑两个维度的情况。
|
||||
|
||||
例如这道题,是先考虑左边呢,还是考虑右边呢?
|
||||
|
||||
@ -54,7 +54,7 @@
|
||||
|
||||
## 周三
|
||||
|
||||
在[贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)中我们模拟了买柠檬水找零的过程。
|
||||
在[贪心算法:柠檬水找零](https://programmercarl.com/0860.柠檬水找零.html)中我们模拟了买柠檬水找零的过程。
|
||||
|
||||
这道题目刚一看,可能会有点懵,这要怎么找零才能保证完整全部账单的找零呢?
|
||||
|
||||
@ -72,11 +72,11 @@
|
||||
|
||||
## 周四
|
||||
|
||||
在[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中,我们再一次遇到了需要考虑两个维度的情况。
|
||||
在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中,我们再一次遇到了需要考虑两个维度的情况。
|
||||
|
||||
之前我们已经做过一道类似的了就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),但本题比分发糖果难不少!
|
||||
之前我们已经做过一道类似的了就是[贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html),但本题比分发糖果难不少!
|
||||
|
||||
[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中依然是要确定一边,然后在考虑另一边,两边一起考虑一定会蒙圈。
|
||||
[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中依然是要确定一边,然后在考虑另一边,两边一起考虑一定会蒙圈。
|
||||
|
||||
那么本题先确定k还是先确定h呢,也就是究竟先按h排序呢,还先按照k排序呢?
|
||||
|
||||
@ -92,7 +92,7 @@
|
||||
|
||||
「代码随想录」里已经讲了十一道贪心题目了,大家可以发现在每一道题目的讲解中,我都是把什么是局部最优,和什么是全局最优说清楚。
|
||||
|
||||
虽然有时候感觉贪心就是常识,但如果真正是常识性的题目,其实是模拟题,就不是贪心算法了!例如[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)中的贪心方法一,其实我就认为不是贪心算法,而是直接从全局最优的角度上来模拟,因为方法里没有体现局部最优的过程。
|
||||
虽然有时候感觉贪心就是常识,但如果真正是常识性的题目,其实是模拟题,就不是贪心算法了!例如[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)中的贪心方法一,其实我就认为不是贪心算法,而是直接从全局最优的角度上来模拟,因为方法里没有体现局部最优的过程。
|
||||
|
||||
而且大家也会发现,贪心并没有想象中的那么简单,贪心往往妙的出其不意,触不及防!哈哈
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)中,我们开始讲解了重叠区间问题,用最少的弓箭射爆所有气球,其本质就是找到最大的重叠区间。
|
||||
在[贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)中,我们开始讲解了重叠区间问题,用最少的弓箭射爆所有气球,其本质就是找到最大的重叠区间。
|
||||
|
||||
按照左边界经行排序后,如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
## 周二
|
||||
|
||||
在[贪心算法:无重叠区间](https://mp.weixin.qq.com/s/oFOEoW-13Bm4mik-aqAOmw)中要去掉最少的区间,来让所有区间没有重叠。
|
||||
在[贪心算法:无重叠区间](https://programmercarl.com/0435.无重叠区间.html)中要去掉最少的区间,来让所有区间没有重叠。
|
||||
|
||||
我来按照右边界排序,从左向右记录非交叉区间的个数。最后用区间总数减去非交叉区间的个数就是需要移除的区间个数了。
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
|
||||

|
||||
|
||||
细心的同学就发现了,此题和 [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像。
|
||||
细心的同学就发现了,此题和 [贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)非常像。
|
||||
|
||||
弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。
|
||||
|
||||
把[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,别可以AC本题。
|
||||
把[贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)代码稍做修改,别可以AC本题。
|
||||
|
||||
修改后的C++代码如下:
|
||||
```CPP
|
||||
@ -58,7 +58,7 @@ public:
|
||||
|
||||
## 周三
|
||||
|
||||
[贪心算法:划分字母区间](https://mp.weixin.qq.com/s/pdX4JwV1AOpc_m90EcO2Hw)中我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
|
||||
[贪心算法:划分字母区间](https://programmercarl.com/0763.划分字母区间.html)中我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。
|
||||
|
||||
这道题目leetcode上标的是贪心,其实我不认识是贪心,因为没感受到局部最优和全局最优的关系。
|
||||
|
||||
@ -76,7 +76,7 @@ public:
|
||||
|
||||
## 周四
|
||||
|
||||
[贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)中要合并所有重叠的区间。
|
||||
[贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html)中要合并所有重叠的区间。
|
||||
|
||||
相信如果录友们前几天区间问题的题目认真练习了,今天题目就应该算简单一些了。
|
||||
|
||||
@ -95,7 +95,7 @@ public:
|
||||
|
||||
其实很多区间的合并操作看起来都是常识,其实贪心算法有时候就是常识,哈哈,但也别小看了贪心算法。
|
||||
|
||||
在[贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)中就说过,对于贪心算法,很多同学都是:「如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了」。
|
||||
在[贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html)中就说过,对于贪心算法,很多同学都是:「如果能凭常识直接做出来,就会感觉不到自己用了贪心, 一旦第一直觉想不出来, 可能就一直想不出来了」。
|
||||
|
||||
所以还是要多看多做多练习!
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
在[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)中我们讲解了动态规划的基础知识。
|
||||
在[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中我们讲解了动态规划的基础知识。
|
||||
|
||||
首先讲一下动规和贪心的区别,其实大家不用太强调理论上的区别,做做题,就感受出来了。
|
||||
|
||||
@ -33,13 +33,13 @@
|
||||
|
||||
## 周二
|
||||
|
||||
这道题目[动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w)是当之无愧的动规入门题。
|
||||
这道题目[动态规划:斐波那契数](https://programmercarl.com/0509.斐波那契数.html)是当之无愧的动规入门题。
|
||||
|
||||
简单题,我们就是用来了解方法论的,用动规五部曲走一遍,题目其实已经把递推公式,和dp数组如何初始化都给我们了。
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) 这道题目其实就是斐波那契数列。
|
||||
[动态规划:爬楼梯](https://programmercarl.com/0070.爬楼梯.html) 这道题目其实就是斐波那契数列。
|
||||
|
||||
但正常思考过程应该是推导完递推公式之后,发现这是斐波那契,而不是上来就知道这是斐波那契。
|
||||
|
||||
@ -98,11 +98,11 @@ public:
|
||||
|
||||
这道绝佳的面试题我没有用过,如果录友们有面试别人的需求,就把这个套路拿去吧,哈哈哈。
|
||||
|
||||
我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获!
|
||||
我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获!
|
||||
|
||||
## 周四
|
||||
|
||||
这道题目[动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA)就是在爬台阶的基础上加了一个花费,
|
||||
这道题目[动态规划:使用最小花费爬楼梯](https://programmercarl.com/0746.使用最小花费爬楼梯.html)就是在爬台阶的基础上加了一个花费,
|
||||
|
||||
这道题描述也确实有点魔幻。
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)中求从出发点到终点有几种路径,只能向下或者向右移动一步。
|
||||
[动态规划:不同路径](https://programmercarl.com/0062.不同路径.html)中求从出发点到终点有几种路径,只能向下或者向右移动一步。
|
||||
|
||||
我们提供了三种方法,但重点讲解的还是动规,也是需要重点掌握的。
|
||||
|
||||
@ -35,7 +35,7 @@ for (int i = 1; i < m; i++) {
|
||||
|
||||
## 周二
|
||||
|
||||
[动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww)相对于[动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)添加了障碍。
|
||||
[动态规划:不同路径还不够,要有障碍!](https://programmercarl.com/0063.不同路径II.html)相对于[动态规划:不同路径](https://programmercarl.com/0062.不同路径.html)添加了障碍。
|
||||
|
||||
dp[i][j]定义依然是:表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径。
|
||||
|
||||
@ -78,7 +78,7 @@ for (int i = 1; i < m; i++) {
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A)给出一个整数,问有多少种拆分的方法。
|
||||
[动态规划:整数拆分,你要怎么拆?](https://programmercarl.com/0343.整数拆分.html)给出一个整数,问有多少种拆分的方法。
|
||||
|
||||
这道题目就有点难度了,题目中dp我也给出了两种方法,但通过两种方法的比较可以看出,对dp数组定义的理解,以及dp数组初始化的重要性。
|
||||
|
||||
@ -121,7 +121,7 @@ for (int i = 3; i <= n ; i++) {
|
||||
|
||||
**或者也可以理解j是拆分i的第一个整数**。
|
||||
|
||||
[动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A)总结里,我也给出了递推公式dp[i] = max(dp[i], dp[i - j] * dp[j])这种写法。
|
||||
[动态规划:整数拆分,你要怎么拆?](https://programmercarl.com/0343.整数拆分.html)总结里,我也给出了递推公式dp[i] = max(dp[i], dp[i - j] * dp[j])这种写法。
|
||||
|
||||
对于这种写法,一位录友总结的很好,意思就是:如果递推公式是dp[i-j] * dp[j],这样就相当于强制把一个数至少拆分成四份。
|
||||
|
||||
@ -129,7 +129,7 @@ dp[i-j]至少是两个数的乘积,dp[j]又至少是两个数的乘积,但
|
||||
|
||||
## 周四
|
||||
|
||||
[动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw)给出n个不同的节点求能组成多少个不同二叉搜索树。
|
||||
[动态规划:不同的二叉搜索树](https://programmercarl.com/0096.不同的二叉搜索树.html)给出n个不同的节点求能组成多少个不同二叉搜索树。
|
||||
|
||||
这道题目还是比较难的,想到用动态规划的方法就很不容易了!
|
||||
|
||||
@ -145,7 +145,7 @@ n为5时候的dp数组状态如图:
|
||||
|
||||
## 总结
|
||||
|
||||
本周题目已经开始点难度了,特别是[动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw)这道题目,明显感觉阅读量很低,可能是因为确实有点难吧。
|
||||
本周题目已经开始点难度了,特别是[动态规划:不同的二叉搜索树](https://programmercarl.com/0096.不同的二叉搜索树.html)这道题目,明显感觉阅读量很低,可能是因为确实有点难吧。
|
||||
|
||||
我现在也陷入了纠结,题目一简单,就会有录友和我反馈说题目太简单了,题目一难,阅读量就特别低。
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中,我们开始介绍了背包问题。
|
||||
[动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)中,我们开始介绍了背包问题。
|
||||
|
||||
首先对于背包的所有问题中,01背包是最最基础的,其他背包也是在01背包的基础上稍作变化。
|
||||
|
||||
@ -75,7 +75,7 @@ for(int i = 1; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
## 周二
|
||||
|
||||
[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA)中把01背包的一维dp数组(滚动数组)实现详细讲解了一遍。
|
||||
[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中把01背包的一维dp数组(滚动数组)实现详细讲解了一遍。
|
||||
|
||||
分析一下和二维dp数组有什么区别,在初始化和遍历顺序上又有什么差异?
|
||||
|
||||
@ -125,7 +125,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)中我们开始用01背包来解决问题。
|
||||
[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)中我们开始用01背包来解决问题。
|
||||
|
||||
只有确定了如下四点,才能把01背包问题套到本题上来。
|
||||
|
||||
@ -138,11 +138,11 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
## 周四
|
||||
|
||||
[动态规划:1049. 最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg)这道题目其实和[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)是非常像的。
|
||||
[动态规划:1049. 最后一块石头的重量 II](https://programmercarl.com/1049.最后一块石头的重量II.html)这道题目其实和[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)是非常像的。
|
||||
|
||||
本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,这样就化解成01背包问题了。
|
||||
|
||||
[动态规划:416. 分割等和子集](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ)相当于是求背包是否正好装满,而本题是求背包最多能装多少。
|
||||
[动态规划:416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)相当于是求背包是否正好装满,而本题是求背包最多能装多少。
|
||||
|
||||
这两道题目是对dp[target]的处理方式不同。这也考验的对dp[i]定义的理解。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw)要求在数列之间加入+ 或者 -,使其和为S。
|
||||
[动态规划:目标和!](https://programmercarl.com/0494.目标和.html)要求在数列之间加入+ 或者 -,使其和为S。
|
||||
|
||||
所有数的总和为sum,假设加法的总和为x,那么可以推出x = (S + sum) / 2。
|
||||
|
||||
@ -39,7 +39,7 @@ dp数组状态变化如下:
|
||||
|
||||
## 周二
|
||||
|
||||
这道题目[动态规划:一和零!](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw)算有点难度。
|
||||
这道题目[动态规划:一和零!](https://programmercarl.com/0474.一和零.html)算有点难度。
|
||||
|
||||
**不少同学都以为是多重背包,其实这是一道标准的01背包**。
|
||||
|
||||
@ -78,7 +78,7 @@ dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
|
||||
|
||||
此时01背包我们就讲完了,正式开始完全背包。
|
||||
|
||||
在[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)中我们讲解了完全背包的理论基础。
|
||||
在[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)中我们讲解了完全背包的理论基础。
|
||||
|
||||
其实完全背包和01背包区别就是完全背包的物品是无限数量。
|
||||
|
||||
@ -100,7 +100,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
**那么为什么要先遍历物品,在遍历背包呢?** (灵魂拷问)
|
||||
|
||||
其实对于纯完全背包,先遍历物品,再遍历背包 与 先遍历背包,再遍历物品都是可以的。我在文中[动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw)也给出了详细的解释。
|
||||
其实对于纯完全背包,先遍历物品,再遍历背包 与 先遍历背包,再遍历物品都是可以的。我在文中[动态规划:关于完全背包,你该了解这些!](https://programmercarl.com/背包问题理论基础完全背包.html)也给出了详细的解释。
|
||||
|
||||
这个细节是很多同学忽略掉的点,其实也不算细节了,**相信不少同学在写背包的时候,两层for循环的先后循序搞不清楚,靠感觉来的**。
|
||||
|
||||
@ -110,7 +110,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
## 周四
|
||||
|
||||
在[动态规划:给你一些零钱,你要怎么凑?](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)中就是给你一堆零钱(零钱个数无限),为凑成amount的组合数有几种。
|
||||
在[动态规划:给你一些零钱,你要怎么凑?](https://programmercarl.com/0518.零钱兑换II.html)中就是给你一堆零钱(零钱个数无限),为凑成amount的组合数有几种。
|
||||
|
||||
**注意这里组合数和排列数的区别!**
|
||||
|
||||
@ -134,7 +134,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品
|
||||
|
||||
其实这是一种错觉,或者说对动规理解的不够深入!
|
||||
|
||||
我在动规专题开篇介绍[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)中就强调了 **递推公式仅仅是 动规五部曲里的一小部分, dp数组的定义、初始化、遍历顺序,哪一点没有搞透的话,即使知道递推公式,遇到稍稍难一点的动规题目立刻会感觉写不出来了**。
|
||||
我在动规专题开篇介绍[关于动态规划,你该了解这些!](https://programmercarl.com/动态规划理论基础.html)中就强调了 **递推公式仅仅是 动规五部曲里的一小部分, dp数组的定义、初始化、遍历顺序,哪一点没有搞透的话,即使知道递推公式,遇到稍稍难一点的动规题目立刻会感觉写不出来了**。
|
||||
|
||||
此时相信大家对动规五部曲也有更深的理解了,同样也验证了Carl之前讲过的:**简单题是用来学习方法论的,而遇到难题才体现出方法论的重要性!**
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。
|
||||
[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)中给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数(顺序不同的序列被视作不同的组合)。
|
||||
|
||||
题目面试虽然是组合,但又强调顺序不同的序列被视作不同的组合,其实这道题目求的是排列数!
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
这个和前上周讲的组合问题又不一样,关键就体现在遍历顺序上!
|
||||
|
||||
在[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) 中就已经讲过了。
|
||||
在[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html) 中就已经讲过了。
|
||||
|
||||
**如果求组合数就是外层for循环遍历物品,内层for遍历背包**。
|
||||
|
||||
@ -40,7 +40,7 @@ public:
|
||||
|
||||
## 周二
|
||||
|
||||
爬楼梯之前我们已经做过了,就是斐波那契数列,很好解,但[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)中我们进阶了一下。
|
||||
爬楼梯之前我们已经做过了,就是斐波那契数列,很好解,但[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html)中我们进阶了一下。
|
||||
|
||||
改为:每次可以爬 1 、 2、.....、m 个台阶。问有多少种不同的方法可以爬到楼顶呢?
|
||||
|
||||
@ -53,7 +53,7 @@ public:
|
||||
**此时大家应该发现这就是一个完全背包问题了!**
|
||||
|
||||
|
||||
和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)基本就是一道题了,遍历顺序也是一样一样的!
|
||||
和昨天的题目[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)基本就是一道题了,遍历顺序也是一样一样的!
|
||||
|
||||
代码如下:
|
||||
```CPP
|
||||
@ -77,7 +77,7 @@ public:
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:322.零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。
|
||||
[动态规划:322.零钱兑换](https://programmercarl.com/0322.零钱兑换.html)给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数(每种硬币的数量是无限的)。
|
||||
|
||||
这里我们都知道这是完全背包。
|
||||
|
||||
@ -137,10 +137,10 @@ public:
|
||||
|
||||
## 周四
|
||||
|
||||
[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。
|
||||
[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html)给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少(平方数可以重复使用)。
|
||||
|
||||
|
||||
如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) 一样一样的。
|
||||
如果按顺序把前面的文章都看了,这道题目就是简单题了。 dp[i]的定义,递推公式,初始化,遍历顺序,都是和[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html) 一样一样的。
|
||||
|
||||
要是没有前面的基础上来做这道题,那这道题目就有点难度了。
|
||||
|
||||
@ -193,9 +193,9 @@ public:
|
||||
|
||||
我这里做一下总结:
|
||||
|
||||
求组合数:[动态规划:518.零钱兑换II](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ)
|
||||
求排列数:[动态规划:377. 组合总和 Ⅳ](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA)
|
||||
求最小数:[动态规划:322. 零钱兑换](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ)、[动态规划:279.完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ)
|
||||
求组合数:[动态规划:518.零钱兑换II](https://programmercarl.com/0518.零钱兑换II.html)
|
||||
求排列数:[动态规划:377. 组合总和 Ⅳ](https://programmercarl.com/0377.组合总和Ⅳ.html)、[动态规划:70. 爬楼梯进阶版(完全背包)](https://programmercarl.com/0070.爬楼梯完全背包版本.html)
|
||||
求最小数:[动态规划:322. 零钱兑换](https://programmercarl.com/0322.零钱兑换.html)、[动态规划:279.完全平方数](https://programmercarl.com/0279.完全平方数.html)
|
||||
|
||||
此时我们就已经把完全背包的遍历顺序研究的透透的了!
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)中就是给一个数组相邻之间不能连着偷,如果偷才能得到最大金钱。
|
||||
[动态规划:开始打家劫舍!](https://programmercarl.com/0198.打家劫舍.html)中就是给一个数组相邻之间不能连着偷,如果偷才能得到最大金钱。
|
||||
|
||||
1. 确定dp数组含义
|
||||
|
||||
@ -35,7 +35,7 @@ dp[1] = max(nums[0], nums[1]);
|
||||
|
||||
## 周二
|
||||
|
||||
[动态规划:继续打家劫舍!](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ)就是数组成环了,然后相邻的不能连着偷。
|
||||
[动态规划:继续打家劫舍!](https://programmercarl.com/0213.打家劫舍II.html)就是数组成环了,然后相邻的不能连着偷。
|
||||
|
||||
这里主要考虑清楚三种情况:
|
||||
|
||||
@ -61,11 +61,11 @@ dp[1] = max(nums[0], nums[1]);
|
||||
|
||||
所以我在本文重点强调了情况一二三是“考虑”的范围,而具体房间偷与不偷交给递推公式去抉择。
|
||||
|
||||
剩下的就和[动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw)是一个逻辑了。
|
||||
剩下的就和[动态规划:开始打家劫舍!](https://programmercarl.com/0198.打家劫舍.html)是一个逻辑了。
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:还要打家劫舍!](https://mp.weixin.qq.com/s/BOJ1lHsxbQxUZffXlgglEQ)这次是在一颗二叉树上打家劫舍了,条件还是一样的,相临的不能偷。
|
||||
[动态规划:还要打家劫舍!](https://programmercarl.com/0337.打家劫舍III.html)这次是在一颗二叉树上打家劫舍了,条件还是一样的,相临的不能偷。
|
||||
|
||||
这道题目是树形DP的入门题目,其实树形DP其实就是在树上进行递推公式的推导,没有什么神秘的。
|
||||
|
||||
@ -184,14 +184,14 @@ return {val2, val1};
|
||||
|
||||
因为平时我们习惯了在一维数组或者二维数组上推导公式,一下子换成了树,就需要对树的遍历方式足够了解!
|
||||
|
||||
大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈
|
||||
大家还记不记得我在讲解贪心专题的时候,讲到这道题目:[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),这也是贪心算法在树上的应用。**那我也可以把这个算法起一个名字,叫做树形贪心**,哈哈哈
|
||||
|
||||
“树形贪心”词汇从此诞生,来自「代码随想录」
|
||||
|
||||
|
||||
## 周四
|
||||
|
||||
[动态规划:买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) 一段时间,只能买买一次,问最大收益。
|
||||
[动态规划:买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html) 一段时间,只能买买一次,问最大收益。
|
||||
|
||||
这里我给出了三中解法:
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
## 周一
|
||||
|
||||
[动态规划:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w)中股票可以买买多了次!
|
||||
[动态规划:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html)中股票可以买买多了次!
|
||||
|
||||
这也是和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的唯一区别(注意只有一只股票,所以再次购买前要出售掉之前的股票)
|
||||
这也是和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的唯一区别(注意只有一只股票,所以再次购买前要出售掉之前的股票)
|
||||
|
||||
重点在于递推公式公式的不同。
|
||||
|
||||
@ -22,7 +22,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
|
||||
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
|
||||
```
|
||||
|
||||
大家可以发现本题和[121. 买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ)的代码几乎一样,唯一的区别在:
|
||||
大家可以发现本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在:
|
||||
|
||||
```
|
||||
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
|
||||
@ -32,7 +32,7 @@ dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
|
||||
|
||||
## 周二
|
||||
|
||||
[动态规划:买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)中最多只能完成两笔交易。
|
||||
[动态规划:买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)中最多只能完成两笔交易。
|
||||
|
||||
**这意味着可以买卖一次,可以买卖两次,也可以不买卖**。
|
||||
|
||||
@ -85,9 +85,9 @@ dp[0][4] = 0;
|
||||
|
||||
## 周三
|
||||
|
||||
[动态规划:买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw)最多可以完成 k 笔交易。
|
||||
[动态规划:买卖股票的最佳时机IV](https://programmercarl.com/0188.买卖股票的最佳时机IV.html)最多可以完成 k 笔交易。
|
||||
|
||||
相对于上一道[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg),本题需要通过前两次的交易,来类比前k次的交易
|
||||
相对于上一道[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html),本题需要通过前两次的交易,来类比前k次的交易
|
||||
|
||||
|
||||
1. 确定dp数组以及下标的含义
|
||||
@ -117,7 +117,7 @@ for (int j = 0; j < 2 * k - 1; j += 2) {
|
||||
}
|
||||
```
|
||||
|
||||
**本题和[动态规划:123.买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。
|
||||
**本题和[动态规划:123.买卖股票的最佳时机III](https://programmercarl.com/0123.买卖股票的最佳时机III.html)最大的区别就是这里要类比j为奇数是买,偶数是卖剩的状态**。
|
||||
|
||||
3. dp数组如何初始化
|
||||
|
||||
@ -147,9 +147,9 @@ for (int j = 1; j < 2 * k; j += 2) {
|
||||
|
||||
## 周四
|
||||
|
||||
[动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/IgC0iWWCDpYL9ZbTHGHgfw)尽可能地完成更多的交易(多次买卖一支股票),但有冷冻期,冷冻期为1天
|
||||
[动态规划:最佳买卖股票时机含冷冻期](https://programmercarl.com/0309.最佳买卖股票时机含冷冻期.html)尽可能地完成更多的交易(多次买卖一支股票),但有冷冻期,冷冻期为1天
|
||||
|
||||
相对于[动态规划:122.买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w),本题加上了一个冷冻期
|
||||
相对于[动态规划:122.买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II(动态规划).html),本题加上了一个冷冻期
|
||||
|
||||
|
||||
**本题则需要第三个状态:不持有股票(冷冻期)的最多现金**。
|
||||
|
@ -4,15 +4,15 @@
|
||||
|
||||
**注意这个周末总结和系列总结还是不一样的(二叉树还远没有结束),这个总结是针对留言疑问以及刷题群里讨论内容的归纳。**
|
||||
|
||||
1. [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA)
|
||||
2. [二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)
|
||||
3. [二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)
|
||||
4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/ATQMPCpBlaAgrqdLDMVPZA)
|
||||
5. [二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/4-bDKi7SdwfBGRm9FYduiA)
|
||||
6. [二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/jG0MgYR9DoUMYcRRF7magw)
|
||||
1. [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)
|
||||
2. [二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)
|
||||
3. [二叉树:听说递归能做的,栈也能做!](https://programmercarl.com/二叉树的迭代遍历.html)
|
||||
4. [二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)
|
||||
5. [二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)
|
||||
6. [二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html)
|
||||
|
||||
|
||||
## [关于二叉树,你该了解这些!](https://mp.weixin.qq.com/s/q_eKfL8vmSbSFcptZ3aeRA)
|
||||
## [关于二叉树,你该了解这些!](https://programmercarl.com/二叉树理论基础.html)
|
||||
|
||||
有同学会把红黑树和二叉平衡搜索树弄分开了,其实红黑树就是一种二叉平衡搜索树,这两个树不是独立的,所以C++中map、multimap、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树。
|
||||
|
||||
@ -51,18 +51,18 @@ a->right = NULL;
|
||||
|
||||
morris遍历是二叉树遍历算法的超强进阶算法,morris遍历可以将非递归遍历中的空间复杂度降为O(1),感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
|
||||
|
||||
## [二叉树的递归遍历](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)
|
||||
## [二叉树的递归遍历](https://programmercarl.com/二叉树的递归遍历.html)
|
||||
|
||||
在[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA)中讲到了递归三要素,以及前中后序的递归写法。
|
||||
在[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html)中讲到了递归三要素,以及前中后序的递归写法。
|
||||
|
||||
文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://mp.weixin.qq.com/s/Ww60X5mIKWdMQV4cN3ejOA),依然可以解决n叉树的前后序遍历,在leetcode上分别是
|
||||
文章中我给出了leetcode上三道二叉树的前中后序题目,但是看完[二叉树:一入递归深似海,从此offer是路人](https://programmercarl.com/二叉树的递归遍历.html),依然可以解决n叉树的前后序遍历,在leetcode上分别是
|
||||
|
||||
* 589. N叉树的前序遍历
|
||||
* 590. N叉树的后序遍历
|
||||
|
||||
大家可以再去把这两道题目做了。
|
||||
|
||||
## [二叉树的非递归遍历](https://mp.weixin.qq.com/s/OH7aCVJ5-Gi32PkNCoZk4A)
|
||||
## [二叉树的非递归遍历](https://programmercarl.com/二叉树的迭代遍历.html)
|
||||
|
||||
细心的同学发现文中前后序遍历空节点是入栈的,其实空节点入不入栈都差不多,但感觉空节点不入栈确实清晰一些,符合文中动画的演示。
|
||||
|
||||
@ -125,7 +125,7 @@ public:
|
||||
|
||||
## 周四
|
||||
|
||||
在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)中我们使用空节点作为标记,给出了统一的前中后序迭代法。
|
||||
在[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://programmercarl.com/二叉树的统一迭代法.html)中我们使用空节点作为标记,给出了统一的前中后序迭代法。
|
||||
|
||||
此时又多了一种前中后序的迭代写法,那么有同学问了:前中后序迭代法是不是一定要统一来写,这样才算是规范。
|
||||
|
||||
@ -135,7 +135,7 @@ public:
|
||||
|
||||
## 周五
|
||||
|
||||
在[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。
|
||||
在[二叉树:层序遍历登场!](https://programmercarl.com/0102.二叉树的层序遍历.html)中我们介绍了二叉树的另一种遍历方式(图论中广度优先搜索在二叉树上的应用)即:层序遍历。
|
||||
|
||||
看完这篇文章,去leetcode上怒刷五题,文章中 编号107题目的样例图放错了(原谅我匆忙之间总是手抖),但不影响大家理解。
|
||||
|
||||
@ -145,7 +145,7 @@ public:
|
||||
|
||||
## 周六
|
||||
|
||||
在[二叉树:你真的会翻转二叉树么?](https://mp.weixin.qq.com/s/6gY1MiXrnm-khAAJiIb5Bg)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获!
|
||||
在[二叉树:你真的会翻转二叉树么?](https://programmercarl.com/0226.翻转二叉树.html)中我们把翻转二叉树这么一道简单又经典的问题,充分的剖析了一波,相信就算做过这道题目的同学,看完本篇之后依然有所收获!
|
||||
|
||||
|
||||
**文中我指的是递归的中序遍历是不行的,因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。**
|
||||
|
@ -46,7 +46,7 @@ for (int i = 0; i < a.size(); i++) {
|
||||
|
||||
# 要不要使用库函数
|
||||
|
||||
在文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中强调了**打基础的时候,不要太迷恋于库函数。**
|
||||
在文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)中强调了**打基础的时候,不要太迷恋于库函数。**
|
||||
|
||||
甚至一些同学习惯于调用substr,split,reverse之类的库函数,却不知道其实现原理,也不知道其时间复杂度,这样实现出来的代码,如果在面试现场,面试官问:“分析其时间复杂度”的话,一定会一脸懵逼!
|
||||
|
||||
@ -57,15 +57,15 @@ for (int i = 0; i < a.size(); i++) {
|
||||
# 双指针法
|
||||
|
||||
|
||||
在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。**
|
||||
在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。**
|
||||
|
||||
接着在[字符串:替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
|
||||
接着在[字符串:替换空格](https://programmercarl.com/剑指Offer05.替换空格.html),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
|
||||
|
||||
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
|
||||
|
||||
那么针对数组删除操作的问题,其实在[27. 移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中就已经提到了使用双指针法进行移除操作。
|
||||
那么针对数组删除操作的问题,其实在[27. 移除元素](https://programmercarl.com/0027.移除元素.html)中就已经提到了使用双指针法进行移除操作。
|
||||
|
||||
同样的道理在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
|
||||
同样的道理在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
|
||||
|
||||
一些同学会使用for循环里调用库函数erase来移除元素,这其实是O(n^2)的操作,因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。
|
||||
|
||||
@ -73,7 +73,7 @@ for (int i = 0; i < a.size(); i++) {
|
||||
|
||||
在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。
|
||||
|
||||
[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
|
||||
[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
|
||||
|
||||
其实**当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章**。
|
||||
|
||||
@ -81,26 +81,26 @@ for (int i = 0; i < a.size(); i++) {
|
||||
|
||||
因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。
|
||||
|
||||
在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
|
||||
在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
|
||||
|
||||
这道题目通过 **先整体反转再局部反转**,实现了反转字符串里的单词。
|
||||
|
||||
后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。
|
||||
|
||||
在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们通过**先局部反转再整体反转**达到了左旋的效果。
|
||||
在[字符串:反转个字符串还有这个用处?](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)中,我们通过**先局部反转再整体反转**达到了左旋的效果。
|
||||
|
||||
# KMP
|
||||
|
||||
KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。**
|
||||
|
||||
KMP的精髓所在就是前缀表,在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。
|
||||
KMP的精髓所在就是前缀表,在[KMP精讲](https://programmercarl.com/0028.实现strStr.html)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。
|
||||
|
||||
前缀表:起始位置到下表i之前(包括i)的子串中,有多大长度的相同前缀后缀。
|
||||
|
||||
那么使用KMP可以解决两类经典问题:
|
||||
|
||||
1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)
|
||||
2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/32Pve4j8IWvdgxYEZdTeFg)
|
||||
1. 匹配问题:[28. 实现 strStr()](https://programmercarl.com/0028.实现strStr.html)
|
||||
2. 重复子串问题:[459.重复的子字符串](https://programmercarl.com/0459.重复的子字符串.html)
|
||||
|
||||
再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。
|
||||
|
||||
@ -108,7 +108,7 @@ KMP的精髓所在就是前缀表,在[KMP精讲](https://mp.weixin.qq.com/s/Mo
|
||||
|
||||
后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。
|
||||
|
||||
然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。
|
||||
然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[KMP精讲](https://programmercarl.com/0028.实现strStr.html)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。
|
||||
|
||||
其中主要**理解j=next[x]这一步最为关键!**
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
# 栈与队列的理论基础
|
||||
|
||||
首先我们在[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)中讲解了栈和队列的理论基础。
|
||||
首先我们在[栈与队列:来看看栈和队列不为人知的一面](https://programmercarl.com/栈与队列理论基础.html)中讲解了栈和队列的理论基础。
|
||||
|
||||
里面提到了灵魂四问:
|
||||
|
||||
@ -33,9 +33,9 @@
|
||||
|
||||
大家还是要多多重视起来!
|
||||
|
||||
了解了栈与队列基础之后,那么可以用[栈与队列:栈实现队列](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg) 和 [栈与队列:队列实现栈](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw) 来练习一下栈与队列的基本操作。
|
||||
了解了栈与队列基础之后,那么可以用[栈与队列:栈实现队列](https://programmercarl.com/0232.用栈实现队列.html) 和 [栈与队列:队列实现栈](https://programmercarl.com/0225.用队列实现栈.html) 来练习一下栈与队列的基本操作。
|
||||
|
||||
值得一提的是,用[栈与队列:用队列实现栈还有点别扭](https://mp.weixin.qq.com/s/yzn6ktUlL-vRG3-m5a8_Yw)中,其实只用一个队列就够了。
|
||||
值得一提的是,用[栈与队列:用队列实现栈还有点别扭](https://programmercarl.com/0225.用队列实现栈.html)中,其实只用一个队列就够了。
|
||||
|
||||
**一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时在去弹出元素就是栈的顺序了。**
|
||||
|
||||
@ -63,7 +63,7 @@ cd a/b/c/../../
|
||||
|
||||
## 括号匹配问题
|
||||
|
||||
在[栈与队列:系统中处处都是栈的应用](https://mp.weixin.qq.com/s/nLlmPMsDCIWSqAtr0jbrpQ)中我们讲解了括号匹配问题。
|
||||
在[栈与队列:系统中处处都是栈的应用](https://programmercarl.com/0020.有效的括号.html)中我们讲解了括号匹配问题。
|
||||
|
||||
**括号匹配是使用栈解决的经典问题。**
|
||||
|
||||
@ -79,23 +79,23 @@ cd a/b/c/../../
|
||||
|
||||
## 字符串去重问题
|
||||
|
||||
在[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中讲解了字符串去重问题。
|
||||
在[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中讲解了字符串去重问题。
|
||||
1047. 删除字符串中的所有相邻重复项
|
||||
|
||||
思路就是可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
|
||||
|
||||
## 逆波兰表达式问题
|
||||
|
||||
在[栈与队列:有没有想过计算机是如何处理表达式的?](https://mp.weixin.qq.com/s/hneh2nnLT91rR8ms2fm_kw)中讲解了求逆波兰表达式。
|
||||
在[栈与队列:有没有想过计算机是如何处理表达式的?](https://programmercarl.com/0150.逆波兰表达式求值.html)中讲解了求逆波兰表达式。
|
||||
|
||||
本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[栈与队列:匹配问题都是栈的强项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中的对对碰游戏是不是就非常像了。**
|
||||
本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[栈与队列:匹配问题都是栈的强项](https://programmercarl.com/1047.删除字符串中的所有相邻重复项.html)中的对对碰游戏是不是就非常像了。**
|
||||
|
||||
|
||||
# 队列的经典题目
|
||||
|
||||
## 滑动窗口最大值问题
|
||||
|
||||
在[栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://mp.weixin.qq.com/s/8c6l2bO74xyMjph09gQtpA)中讲解了一种数据结构:单调队列。
|
||||
在[栈与队列:滑动窗口里求最大值引出一个重要数据结构](https://programmercarl.com/0239.滑动窗口最大值.html)中讲解了一种数据结构:单调队列。
|
||||
|
||||
这道题目还是比较绕的,如果第一次遇到这种题目,需要反复琢磨琢磨
|
||||
|
||||
@ -123,7 +123,7 @@ cd a/b/c/../../
|
||||
|
||||
## 求前 K 个高频元素
|
||||
|
||||
在[栈与队列:求前 K 个高频元素和队列有啥关系?](https://mp.weixin.qq.com/s/8hMwxoE_BQRbzCc7CA8rng)中讲解了求前 K 个高频元素。
|
||||
在[栈与队列:求前 K 个高频元素和队列有啥关系?](https://programmercarl.com/0347.前K个高频元素.html)中讲解了求前 K 个高频元素。
|
||||
|
||||
通过求前 K 个高频元素,引出另一种队列就是**优先级队列**。
|
||||
|
||||
|
@ -174,7 +174,7 @@ vector<int> postorderTraversal(TreeNode* root) {
|
||||
```
|
||||
### 广度优先遍历(队列)
|
||||
|
||||
相关题解:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
相关题解:[0102.二叉树的层序遍历](https://programmercarl.com/0102.二叉树的层序遍历.html)
|
||||
|
||||
```
|
||||
vector<vector<int>> levelOrder(TreeNode* root) {
|
||||
@ -202,13 +202,13 @@ vector<vector<int>> levelOrder(TreeNode* root) {
|
||||
|
||||
可以直接解决如下题目:
|
||||
|
||||
* [0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
* [0102.二叉树的层序遍历](https://programmercarl.com/0102.二叉树的层序遍历.html)
|
||||
* [0199.二叉树的右视图](https://github.com/youngyangyang04/leetcode/blob/master/problems/0199.二叉树的右视图.md)
|
||||
* [0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md)
|
||||
* [0104.二叉树的最大深度 (迭代法)](https://github.com/youngyangyang04/leetcode/blob/master/problems/0104.二叉树的最大深度.md)
|
||||
* [0104.二叉树的最大深度 (迭代法)](https://programmercarl.com/0104.二叉树的最大深度.html)
|
||||
|
||||
* [0111.二叉树的最小深度(迭代法)]((https://github.com/youngyangyang04/leetcode/blob/master/problems/0111.二叉树的最小深度.md))
|
||||
* [0222.完全二叉树的节点个数(迭代法)](https://github.com/youngyangyang04/leetcode/blob/master/problems/0222.完全二叉树的节点个数.md)
|
||||
* [0111.二叉树的最小深度(迭代法)](https://programmercarl.com/0111.二叉树的最小深度.html)
|
||||
* [0222.完全二叉树的节点个数(迭代法)](https://programmercarl.com/0222.完全二叉树的节点个数.html)
|
||||
|
||||
### 二叉树深度
|
||||
|
||||
|
@ -137,7 +137,7 @@ dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化
|
||||
|
||||
```
|
||||
// 初始化 dp
|
||||
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
|
||||
vector<vector<int>> dp(weight.size(), vector<int>(bagWeight + 1, 0));
|
||||
for (int j = weight[0]; j <= bagWeight; j++) {
|
||||
dp[0][j] = value[0];
|
||||
}
|
||||
@ -230,7 +230,7 @@ void test_2_wei_bag_problem1() {
|
||||
int bagWeight = 4;
|
||||
|
||||
// 二维数组
|
||||
vector<vector<int>> dp(weight.size() + 1, vector<int>(bagWeight + 1, 0));
|
||||
vector<vector<int>> dp(weight.size(), vector<int>(bagWeight + 1, 0));
|
||||
|
||||
// 初始化
|
||||
for (int j = weight[0]; j <= bagWeight; j++) {
|
||||
|
@ -80,7 +80,7 @@ dp状态图如下:
|
||||
* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)
|
||||
* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)
|
||||
|
||||
就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一位dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
|
||||
就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
|
||||
|
||||
**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序同样无所谓!**
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
## 贪心理论基础
|
||||
|
||||
在贪心系列开篇词[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg)中,我们就讲解了大家对贪心的普遍疑惑。
|
||||
在贪心系列开篇词[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html)中,我们就讲解了大家对贪心的普遍疑惑。
|
||||
|
||||
1. 贪心很简单,就是常识?
|
||||
|
||||
@ -48,42 +48,42 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是
|
||||
|
||||
就像是 要用一下 1 + 1 = 2,没有必要再证明一下 1 + 1 究竟为什么等于 2。(例子极端了点,但是这个道理)
|
||||
|
||||
相信大家读完[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),就对贪心有了一个基本的认识了。
|
||||
相信大家读完[关于贪心算法,你该了解这些!](https://programmercarl.com/贪心算法理论基础.html),就对贪心有了一个基本的认识了。
|
||||
|
||||
|
||||
## 贪心简单题
|
||||
|
||||
以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,但我都具体分析了局部最优是什么,全局最优是什么,贪心也要贪的有理有据!
|
||||
|
||||
* [贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw)
|
||||
* [贪心算法:K次取反后最大化的数组和](https://mp.weixin.qq.com/s/dMTzBBVllRm_Z0aaWvYazA)
|
||||
* [贪心算法:柠檬水找零](https://mp.weixin.qq.com/s/0kT4P-hzY7H6Ae0kjQqnZg)
|
||||
* [贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)
|
||||
* [贪心算法:K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)
|
||||
* [贪心算法:柠檬水找零](https://programmercarl.com/0860.柠檬水找零.html)
|
||||
|
||||
|
||||
## 贪心中等题
|
||||
|
||||
贪心中等题,靠常识可能就有点想不出来了。开始初现贪心算法的难度与巧妙之处。
|
||||
|
||||
* [贪心算法:摆动序列](https://mp.weixin.qq.com/s/Xytl05kX8LZZ1iWWqjMoHA)
|
||||
* [贪心算法:单调递增的数字](https://mp.weixin.qq.com/s/TAKO9qPYiv6KdMlqNq_ncg)
|
||||
* [贪心算法:摆动序列](https://programmercarl.com/0376.摆动序列.html)
|
||||
* [贪心算法:单调递增的数字](https://programmercarl.com/0738.单调递增的数字.html)
|
||||
|
||||
### 贪心解决股票问题
|
||||
|
||||
大家都知道股票系列问题是动规的专长,其实用贪心也可以解决,而且还不止就这两道题目,但这两道比较典型,我就拿来单独说一说
|
||||
|
||||
* [贪心算法:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/VsTFA6U96l18Wntjcg3fcg)
|
||||
* [贪心算法:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/olWrUuDEYw2Jx5rMeG7XAg)
|
||||
* [贪心算法:买卖股票的最佳时机II](https://programmercarl.com/0122.买卖股票的最佳时机II.html)
|
||||
* [贪心算法:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费.html)
|
||||
|
||||
### 两个维度权衡问题
|
||||
|
||||
在出现两个维度相互影响的情况时,两边一起考虑一定会顾此失彼,要先确定一个维度,再确定另一个一个维度。
|
||||
|
||||
* [贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)
|
||||
* [贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)
|
||||
* [贪心算法:分发糖果](https://programmercarl.com/0135.分发糖果.html)
|
||||
* [贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)
|
||||
|
||||
在讲解本题的过程中,还强调了编程语言的重要性,模拟插队的时候,使用C++中的list(链表)替代了vector(动态数组),效率会高很多。
|
||||
|
||||
所以在[贪心算法:根据身高重建队列(续集)](https://mp.weixin.qq.com/s/K-pRN0lzR-iZhoi-1FgbSQ)详细讲解了,为什么用list(链表)更快!
|
||||
所以在[贪心算法:根据身高重建队列(续集)](https://programmercarl.com/根据身高重建队列(vector原理讲解).html)详细讲解了,为什么用list(链表)更快!
|
||||
|
||||
**大家也要掌握自己所用的编程语言,理解其内部实现机制,这样才能写出高效的算法!**
|
||||
|
||||
@ -95,21 +95,20 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是
|
||||
|
||||
关于区间问题,大家应该印象深刻,有一周我们专门讲解的区间问题,各种覆盖各种去重。
|
||||
|
||||
* [贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)
|
||||
* [贪心算法:跳跃游戏II](https://mp.weixin.qq.com/s/kJBcsJ46DKCSjT19pxrNYg)
|
||||
* [贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)
|
||||
* [贪心算法:无重叠区间](https://mp.weixin.qq.com/s/oFOEoW-13Bm4mik-aqAOmw)
|
||||
* [贪心算法:划分字母区间](https://mp.weixin.qq.com/s/pdX4JwV1AOpc_m90EcO2Hw)
|
||||
* [贪心算法:合并区间](https://mp.weixin.qq.com/s/royhzEM5tOkUFwUGrNStpw)
|
||||
* [贪心算法:跳跃游戏](https://programmercarl.com/0055.跳跃游戏.html)
|
||||
* [贪心算法:跳跃游戏II](https://programmercarl.com/0045.跳跃游戏II.html)
|
||||
* [贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)
|
||||
* [贪心算法:无重叠区间](https://programmercarl.com/0435.无重叠区间.html)
|
||||
* [贪心算法:划分字母区间](https://programmercarl.com/0763.划分字母区间.html)
|
||||
* [贪心算法:合并区间](https://programmercarl.com/0056.合并区间.html)
|
||||
|
||||
### 其他难题
|
||||
|
||||
[贪心算法:最大子序和](https://mp.weixin.qq.com/s/DrjIQy6ouKbpletQr0g1Fg) 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。
|
||||
[贪心算法:最大子序和](https://programmercarl.com/0053.最大子序和.html) 其实是动态规划的题目,但贪心性能更优,很多同学也是第一次发现贪心能比动规更优的题目。
|
||||
|
||||
[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。
|
||||
|
||||
[贪心算法:加油站](https://mp.weixin.qq.com/s/aDbiNuEZIhy6YKgQXvKELw)可能以为是一道模拟题,但就算模拟其实也不简单,需要把while用的很娴熟。但其实是可以使用贪心给时间复杂度降低一个数量级。
|
||||
|
||||
最后贪心系列压轴题目[贪心算法:我要监控二叉树!](https://mp.weixin.qq.com/s/kCxlLLjWKaE6nifHC3UL2Q),不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。
|
||||
最后贪心系列压轴题目[贪心算法:我要监控二叉树!](https://programmercarl.com/0968.监控二叉树.html),不仅贪心的思路不好想,而且需要对二叉树的操作特别娴熟,这就是典型的交叉类难题了。
|
||||
|
||||
|
||||
## 贪心每周总结
|
||||
@ -120,10 +119,10 @@ Carl个人认为:如果找出局部最优并可以推出全局最优,就是
|
||||
|
||||
所以周总结一定要看!
|
||||
|
||||
* [本周小结!(贪心算法系列一)](https://mp.weixin.qq.com/s/KQ2caT9GoVXgB1t2ExPncQ)
|
||||
* [本周小结!(贪心算法系列二)](https://mp.weixin.qq.com/s/RiQri-4rP9abFmq_mlXNiQ)
|
||||
* [本周小结!(贪心算法系列三)](https://mp.weixin.qq.com/s/JfeuK6KgmifscXdpEyIm-g)
|
||||
* [本周小结!(贪心算法系列四)](https://mp.weixin.qq.com/s/zAMHT6JfB19ZSJNP713CAQ)
|
||||
* [本周小结!(贪心算法系列一)](https://programmercarl.com/周总结/20201126贪心周末总结.html)
|
||||
* [本周小结!(贪心算法系列二)](https://programmercarl.com/周总结/20201203贪心周末总结.html)
|
||||
* [本周小结!(贪心算法系列三)](https://programmercarl.com/周总结/20201217贪心周末总结.html)
|
||||
* [本周小结!(贪心算法系列四)](https://programmercarl.com/周总结/20201224贪心周末总结.html)
|
||||
|
||||
## 总结
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
||||
|
||||
**那么刷题的时候什么时候真的需要数学推导呢?**
|
||||
|
||||
例如这道题目:[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA),这道题不用数学推导一下,就找不出环的起始位置,想试一下就不知道怎么试,这种题目确实需要数学简单推导一下。
|
||||
例如这道题目:[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html),这道题不用数学推导一下,就找不出环的起始位置,想试一下就不知道怎么试,这种题目确实需要数学简单推导一下。
|
||||
|
||||
## 贪心一般解题步骤
|
||||
|
||||
|
Reference in New Issue
Block a user