Skip to content

Commit

Permalink
delete $
Browse files Browse the repository at this point in the history
  • Loading branch information
youngyangyang04 committed Apr 12, 2022
1 parent e790032 commit 804d7e0
Show file tree
Hide file tree
Showing 30 changed files with 111 additions and 123 deletions.
4 changes: 2 additions & 2 deletions problems/0063.不同路径II.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ public:
};
```

* 时间复杂度:$O(n × m)$,n、m 分别为obstacleGrid 长度和宽度
* 空间复杂度:$O(m)$
* 时间复杂度:O(n × m),n、m 分别为obstacleGrid 长度和宽度
* 空间复杂度:O(m)


## 总结
Expand Down
8 changes: 4 additions & 4 deletions problems/0123.买卖股票的最佳时机III.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,8 @@ public:
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n × 5)$
* 时间复杂度:O(n)
* 空间复杂度:O(n × 5)
当然,大家可以看到力扣官方题解里的一种优化空间写法,我这里给出对应的C++版本:
Expand All @@ -173,8 +173,8 @@ public:
};
```

* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n)
* 空间复杂度:O(1)

大家会发现dp[2]利用的是当天的dp[1]。 但结果也是对的。

Expand Down
2 changes: 1 addition & 1 deletion problems/0139.单词拆分.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public:
};
```

* 时间复杂度:O(n^3),因为substr返回子串的副本是$O(n)$的复杂度(这里的n是substring的长度)
* 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)
* 空间复杂度:O(n)


Expand Down
2 changes: 1 addition & 1 deletion problems/0151.翻转字符串里的单词.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ void removeExtraSpaces(string& s) {
逻辑很简单,从前向后遍历,遇到空格了就erase。
如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是$O(n)$的时间复杂度呢。
如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是O(n)的时间复杂度呢。
想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html),最优的算法来移除元素也要O(n)。
Expand Down
4 changes: 2 additions & 2 deletions problems/0343.整数拆分.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ public:
};
```
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$
* 时间复杂度:O(n^2)
* 空间复杂度:O(n)
### 贪心
Expand Down
2 changes: 1 addition & 1 deletion problems/0450.删除二叉搜索树中的节点.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ if (root == nullptr) return root;

* 确定单层递归的逻辑

这里就把平衡二叉树中删除节点遇到的情况都搞清楚
这里就把二叉搜索树中删除节点遇到的情况都搞清楚

有以下五种情况:

Expand Down
2 changes: 1 addition & 1 deletion problems/0503.下一个更大元素II.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public:

这种写法确实比较直观,但做了很多无用操作,例如修改了nums数组,而且最后还要把result数组resize回去。

resize倒是不费时间,是$O(1)$的操作,但扩充nums数组相当于多了一个$O(n)$的操作。
resize倒是不费时间,是O(1)的操作,但扩充nums数组相当于多了一个O(n)的操作。

其实也可以不扩充nums,而是在遍历的过程中模拟走了两边nums。

Expand Down
10 changes: 5 additions & 5 deletions problems/0674.最长连续递增序列.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ public:
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
* 时间复杂度:O(n)
* 空间复杂度:O(n)
### 贪心
Expand All @@ -135,12 +135,12 @@ public:
}
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n)
* 空间复杂度:O(1)

## 总结

本题也是动规里子序列问题的经典题目,但也可以用贪心来做,大家也会发现贪心好像更简单一点,而且空间复杂度仅是$O(1)$
本题也是动规里子序列问题的经典题目,但也可以用贪心来做,大家也会发现贪心好像更简单一点,而且空间复杂度仅是O(1)。

在动规分析中,关键是要理解和[动态规划:300.最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html)的区别。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@

使用贪心算法,的性能是:

* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
* 时间复杂度:O(n)
* 空间复杂度:O(1)

那么我们再来看看是使用动规的方法如何解题。

Expand Down Expand Up @@ -87,8 +87,8 @@ public:
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
* 时间复杂度:O(n)
* 空间复杂度:O(n)
## 其他语言版本
Expand Down
2 changes: 1 addition & 1 deletion problems/0844.比较含退格的字符串.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public:
}
};
```
* 时间复杂度:O(n + m),n为S的长度,m为T的长度 ,也可以理解是$O(n)$的时间复杂度
* 时间复杂度:O(n + m),n为S的长度,m为T的长度 ,也可以理解是O(n)的时间复杂度
* 空间复杂度:O(n + m)

当然以上代码,大家可以发现有重复的逻辑处理S,处理T,可以把这块公共逻辑抽离出来,代码精简如下:
Expand Down
2 changes: 1 addition & 1 deletion problems/0922.按奇偶排序数组II.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public:
* 时间复杂度:O(n)
* 空间复杂度:O(1)
这里时间复杂度并不是$O(n^2)$,因为偶数位和奇数位都只操作一次,不是n/2 * n/2的关系,而是n/2 + n/2的关系!
这里时间复杂度并不是O(n^2),因为偶数位和奇数位都只操作一次,不是n/2 * n/2的关系,而是n/2 + n/2的关系!
## 其他语言版本
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ O(nlogn)的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符

![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png)

至于$O(\log n)$和$O(n^3)$ 等等这些时间复杂度在1s内可以处理的多大的数据规模,大家可以自己写一写代码去测一下了。
至于O(log n)和O(n^3) 等等这些时间复杂度在1s内可以处理的多大的数据规模,大家可以自己写一写代码去测一下了。

# 完整测试代码

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

算法导论给出的解释:**大O用来表示上界的**,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界。

同样算法导论给出了例子:拿插入排序来说,插入排序的时间复杂度我们都说是$O(n^2)$
同样算法导论给出了例子:拿插入排序来说,插入排序的时间复杂度我们都说是O(n^2) 。

输入数据的形式对程序运算时间是有很大影响的,在数据本来有序的情况下时间复杂度是O(n),但如果数据是逆序的话,插入排序的时间复杂度就是O(n^2),也就对于所有输入情况来说,最坏是O(n^2) 的时间复杂度,所以称插入排序的时间复杂度为O(n^2)。

Expand All @@ -44,7 +44,7 @@

![时间复杂度,不同数据规模的差异](https://img-blog.csdnimg.cn/20200728191447384.png)

在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用$O(n^2)$的算法比$O(n)$的更合适(在有常数项的时候)。
在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用O(n^2)的算法比O(n)的更合适(在有常数项的时候)。

就像上图中 O(5n^2) 和 O(100n) 在n为20之前 很明显 O(5n^2)是更优的,所花费的时间也是最少的。

Expand Down Expand Up @@ -125,7 +125,7 @@ O(2 × n^2 + 10 × n + 1000) < O(3 × n^2),所以说最后省略掉常数项

如果是暴力枚举的话,时间复杂度是多少呢,是O(n^2)么?

这里一些同学会忽略了字符串比较的时间消耗,这里并不像int 型数字做比较那么简单,除了n^2次的遍历次数外,字符串比较依然要消耗m次操作(m也就是字母串的长度),所以时间复杂度是$O(m × n × n)$
这里一些同学会忽略了字符串比较的时间消耗,这里并不像int 型数字做比较那么简单,除了n^2次的遍历次数外,字符串比较依然要消耗m次操作(m也就是字母串的长度),所以时间复杂度是O(m × n × n)。

接下来再想一下其他解题思路。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* 什么是大O
* 不同数据规模的差异
* 复杂表达式的化简
* $O(\log n)$中的log是以什么为底?
* O(log n)中的log是以什么为底?
* 举一个例子


Expand All @@ -23,21 +23,21 @@

那么该如何估计程序运行时间呢,通常会估算算法的操作单元数量来代表程序消耗的时间,这里默认CPU的每个单元运行消耗的时间都是相同的。

假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 $O(f(n)$)。
假设算法的问题规模为n,那么操作单元数量便用函数f(n)来表示,随着数据规模n的增大,算法执行时间的增长率和f(n)的增长率相同,这称作为算法的渐近时间复杂度,简称时间复杂度,记为 O(f(n))。

## 什么是大O

这里的大O是指什么呢,说到时间复杂度,**大家都知道$O(n)$,$O(n^2)$,却说不清什么是大O**
这里的大O是指什么呢,说到时间复杂度,**大家都知道O(n)O(n^2),却说不清什么是大O**

算法导论给出的解释:**大O用来表示上界的**,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界。

同样算法导论给出了例子:拿插入排序来说,插入排序的时间复杂度我们都说是$O(n^2)$
同样算法导论给出了例子:拿插入排序来说,插入排序的时间复杂度我们都说是O(n^2) 。

输入数据的形式对程序运算时间是有很大影响的,在数据本来有序的情况下时间复杂度是$O(n)$,但如果数据是逆序的话,插入排序的时间复杂度就是$O(n^2)$,也就对于所有输入情况来说,最坏是$O(n^2)$ 的时间复杂度,所以称插入排序的时间复杂度为$O(n^2)$
输入数据的形式对程序运算时间是有很大影响的,在数据本来有序的情况下时间复杂度是O(n),但如果数据是逆序的话,插入排序的时间复杂度就是O(n^2),也就对于所有输入情况来说,最坏是O(n^2) 的时间复杂度,所以称插入排序的时间复杂度为O(n^2)。

同样的同理再看一下快速排序,都知道快速排序是$O(n\log n)$,但是当数据已经有序情况下,快速排序的时间复杂度是$O(n^2)$ 的,**所以严格从大O的定义来讲,快速排序的时间复杂度应该是$O(n^2)$**
同样的同理再看一下快速排序,都知道快速排序是O(nlogn),但是当数据已经有序情况下,快速排序的时间复杂度是O(n^2) 的,**所以严格从大O的定义来讲,快速排序的时间复杂度应该是O(n^2)**

**但是我们依然说快速排序是$O(n\log n)$的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界**。如图所示:
**但是我们依然说快速排序是O(nlogn)的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界**。如图所示:
![时间复杂度4,一般情况下的时间复杂度](https://img-blog.csdnimg.cn/20200728185745611.png)

我们主要关心的还是一般情况下的数据形式。
Expand All @@ -51,25 +51,25 @@

![时间复杂度,不同数据规模的差异](https://img-blog.csdnimg.cn/20200728191447384.png)

在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用$O(n^2)$的算法比$O(n)$的更合适(在有常数项的时候)。
在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用O(n^2)的算法比O(n)的更合适(在有常数项的时候)。

就像上图中 $O(5n^2)$$O(100n)$ 在n为20之前 很明显 $O(5n^2)$是更优的,所花费的时间也是最少的。
就像上图中 O(5n^2) 和 O(100n) 在n为20之前 很明显 O(5n^2)是更优的,所花费的时间也是最少的。

那为什么在计算时间复杂度的时候要忽略常数项系数呢,也就说$O(100n)$ 就是$O(n)$的时间复杂度,$O(5n^2)$ 就是$O(n^2)$的时间复杂度,而且要默认$O(n)$ 优于$O(n^2)$ 呢 ?
那为什么在计算时间复杂度的时候要忽略常数项系数呢,也就说O(100n) 就是O(n)的时间复杂度,O(5n^2) 就是O(n^2)的时间复杂度,而且要默认O(n) 优于O(n^2) 呢 ?

这里就又涉及到大O的定义,**因为大O就是数据量级突破一个点且数据量级非常大的情况下所表现出的时间复杂度,这个数据量也就是常数项系数已经不起决定性作用的数据量**

例如上图中20就是那个点,n只要大于20 常数项系数已经不起决定性作用了。

**所以我们说的时间复杂度都是省略常数项系数的,是因为一般情况下都是默认数据规模足够的大,基于这样的事实,给出的算法时间复杂的的一个排行如下所示**

O(1)常数阶 < $O(\log n)$对数阶 < $O(n)$线性阶 < $O(n^2)$平方阶 < $O(n^3)$立方阶 < $O(2^n)$指数阶
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n^2)平方阶 < O(n^3)立方阶 < O(2^n)指数阶

但是也要注意大常数,如果这个常数非常大,例如10^7 ,10^9 ,那么常数就是不得不考虑的因素了。

## 复杂表达式的化简

有时候我们去计算时间复杂度的时候发现不是一个简单的$O(n)$ 或者$O(n^2)$, 而是一个复杂的表达式,例如:
有时候我们去计算时间复杂度的时候发现不是一个简单的O(n) 或者O(n^2), 而是一个复杂的表达式,例如:

```
O(2*n^2 + 10*n + 1000)
Expand All @@ -95,19 +95,19 @@ O(n^2 + n)
O(n^2)
```

如果这一步理解有困难,那也可以做提取n的操作,变成$O(n(n+1)$) ,省略加法常数项后也就别变成了:
如果这一步理解有困难,那也可以做提取n的操作,变成O(n(n+1)) ,省略加法常数项后也就别变成了:

```
O(n^2)
```

所以最后我们说:这个算法的算法时间复杂度是$O(n^2)$
所以最后我们说:这个算法的算法时间复杂度是O(n^2) 。


也可以用另一种简化的思路,其实当n大于40的时候, 这个复杂度会恒小于$O(3 × n^2)$
$O(2 × n^2 + 10 × n + 1000) < O(3 × n^2)$,所以说最后省略掉常数项系数最终时间复杂度也是$O(n^2)$
也可以用另一种简化的思路,其实当n大于40的时候, 这个复杂度会恒小于O(3 × n^2),
O(2 × n^2 + 10 × n + 1000) < O(3 × n^2),所以说最后省略掉常数项系数最终时间复杂度也是O(n^2)。

## $O(\log n)$中的log是以什么为底?
## O(logn)中的log是以什么为底?

平时说这个算法的时间复杂度是logn的,那么一定是log 以2为底n的对数么?

Expand All @@ -130,21 +130,21 @@ $O(2 × n^2 + 10 × n + 1000) < O(3 × n^2)$,所以说最后省略掉常数项

通过这道面试题目,来分析一下时间复杂度。题目描述:找出n个字符串中相同的两个字符串(假设这里只有两个相同的字符串)。

如果是暴力枚举的话,时间复杂度是多少呢,是$O(n^2)$么?
如果是暴力枚举的话,时间复杂度是多少呢,是O(n^2)么?

这里一些同学会忽略了字符串比较的时间消耗,这里并不像int 型数字做比较那么简单,除了n^2 次的遍历次数外,字符串比较依然要消耗m次操作(m也就是字母串的长度),所以时间复杂度是$O(m × n × n)$
这里一些同学会忽略了字符串比较的时间消耗,这里并不像int 型数字做比较那么简单,除了n^2 次的遍历次数外,字符串比较依然要消耗m次操作(m也就是字母串的长度),所以时间复杂度是O(m × n × n)。

接下来再想一下其他解题思路。

先排对n个字符串按字典序来排序,排序后n个字符串就是有序的,意味着两个相同的字符串就是挨在一起,然后在遍历一遍n个字符串,这样就找到两个相同的字符串了。

那看看这种算法的时间复杂度,快速排序时间复杂度为$O(n\log n)$,依然要考虑字符串的长度是m,那么快速排序每次的比较都要有m次的字符比较的操作,就是$O(m × n × \log n)$
那看看这种算法的时间复杂度,快速排序时间复杂度为O(nlogn),依然要考虑字符串的长度是m,那么快速排序每次的比较都要有m次的字符比较的操作,就是O(m × n × log n) 。

之后还要遍历一遍这n个字符串找出两个相同的字符串,别忘了遍历的时候依然要比较字符串,所以总共的时间复杂度是 $O(m × n × \log n + n × m)$
之后还要遍历一遍这n个字符串找出两个相同的字符串,别忘了遍历的时候依然要比较字符串,所以总共的时间复杂度是 O(m × n × logn + n × m)。

我们对$O(m × n × \log n + n × m)$ 进行简化操作,把$m × n$提取出来变成 $O(m × n × (\log n + 1)$),再省略常数项最后的时间复杂度是 $O(m × n × \log n)$
我们对O(m × n × log n + n × m) 进行简化操作,把m × n提取出来变成 O(m × n × (logn + 1)),再省略常数项最后的时间复杂度是 O(m × n × log n)。

最后很明显$O(m × n × \log n)$ 要优于$O(m × n × n)$
最后很明显O(m × n × logn) 要优于O(m × n × n)!

所以先把字符串集合排序再遍历一遍找到两个相同字符串的方法要比直接暴力枚举的方式更快。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
# 空间复杂度分析

* [关于时间复杂度,你不知道的都在这里!](https://programmercarl.com/前序/关于时间复杂度,你不知道的都在这里!.html)
* [$O(n)$的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html)
* [O(n)的算法居然超时了,此时的n究竟是多大?](https://programmercarl.com/前序/On的算法居然超时了,此时的n究竟是多大?.html)
* [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)

那么一直还没有讲空间复杂度,所以打算陆续来补上,内容不难,大家可以读一遍文章就有整体的了解了。

什么是空间复杂度呢?

是对一个算法在运行过程中占用内存空间大小的量度,记做$S(n)=O(f(n)$
是对一个算法在运行过程中占用内存空间大小的量度,记做S(n)=O(f(n)。

空间复杂度(Space Complexity)记作S(n) 依然使用大O来表示。利用程序的空间复杂度,可以对程序运行中需要多少内存有个预先估计。

Expand Down Expand Up @@ -41,21 +41,21 @@ for (int i = 0; i < n; i++) {
}

```
第一段代码可以看出,随着n的变化,所需开辟的内存空间并不会随着n的变化而变化。即此算法空间复杂度为一个常量,所以表示为大$O(1)$
第一段代码可以看出,随着n的变化,所需开辟的内存空间并不会随着n的变化而变化。即此算法空间复杂度为一个常量,所以表示为大O(1)。

什么时候的空间复杂度是$O(n)$
什么时候的空间复杂度是O(n)?

当消耗空间和输入参数n保持线性增长,这样的空间复杂度为$O(n)$,来看一下这段C++代码
当消耗空间和输入参数n保持线性增长,这样的空间复杂度为O(n),来看一下这段C++代码
```CPP
int* a = new int(n);
for (int i = 0; i < n; i++) {
a[i] = i;
}
```

我们定义了一个数组出来,这个数组占用的大小为n,虽然有一个for循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,随着n的增大,开辟的内存大小呈线性增长,即 $O(n)$
我们定义了一个数组出来,这个数组占用的大小为n,虽然有一个for循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,随着n的增大,开辟的内存大小呈线性增长,即 O(n)。

其他的 $O(n^2)$, $O(n^3)$ 我想大家应该都可以以此例举出来了,**那么思考一下 什么时候空间复杂度是 $O(\log n)$呢?**
其他的 O(n^2)O(n^3) 我想大家应该都可以以此例举出来了,**那么思考一下 什么时候空间复杂度是 O(logn)呢?**

空间复杂度是logn的情况确实有些特殊,其实是在**递归的时候,会出现空间复杂度为logn的情况**

Expand Down
Loading

0 comments on commit 804d7e0

Please sign in to comment.