优化排版,把复杂度标记为公式

This commit is contained in:
bqlin 2021-12-10 20:07:53 +08:00
parent 973582cd7e
commit 90638af21a
96 changed files with 462 additions and 449 deletions

View File

@ -115,7 +115,7 @@
* 算法性能分析
* [关于时间复杂度,你不知道的都在这里!](./problems/前序/关于时间复杂度,你不知道的都在这里!.md)
* [O(n)的算法居然超时了此时的n究竟是多大](./problems/前序/On的算法居然超时了此时的n究竟是多大.md)
* [$O(n)$的算法居然超时了此时的n究竟是多大](./problems/前序/On的算法居然超时了此时的n究竟是多大.md)
* [通过一道面试题目,讲一讲递归算法的时间复杂度!](./problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md)
* [本周小结!(算法性能分析系列一)](./problems/周总结/20201210复杂度分析周末总结.md)
* [关于空间复杂度,可能有几个疑问?](./problems/前序/关于空间复杂度,可能有几个疑问?.md)

View File

@ -24,7 +24,7 @@
## 思路
很明显暴力的解法是两层for循环查找时间复杂度是O(n^2)。
很明显暴力的解法是两层for循环查找时间复杂度是$O(n^2)$
建议大家做这道题目之前,先做一下这两道
* [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html)
@ -43,9 +43,9 @@ C++中map有三种类型
|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
|---|---| --- |---| --- | --- | ---|
|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(logn)|O(logn) |
|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(logn) |O(logn) |
|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)|
|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | $O(\log n)$|$O(\log n)$ |
|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|$O(\log n)$ |$O(\log n)$ |
|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |$O(1)$ | $O(1)$|
std::unordered_map 底层实现为哈希表std::map 和std::multimap 的底层实现是红黑树。

View File

@ -38,7 +38,7 @@
两层for循环遍历区间起始位置和终止位置然后判断这个区间是不是回文。
时间复杂度O(n^3)
时间复杂度:$O(n^3)$
## 动态规划
@ -205,8 +205,8 @@ public:
```
* 时间复杂度O(n^2)
* 空间复杂度O(n^2)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n^2)$
## 双指针
@ -253,8 +253,8 @@ public:
```
* 时间复杂度O(n^2)
* 空间复杂度O(1)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$

View File

@ -39,7 +39,7 @@
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
时间复杂度可以做到$O(n^2)$,但还是比较费时的,因为不好做剪枝操作。
大家可以尝试使用哈希法写一写,就知道其困难的程度了。
@ -85,7 +85,7 @@ public:
**其实这道题目使用哈希法并不十分合适**因为在去重的操作中有很多细节需要注意在面试中很难直接写出没有bug的代码。
而且使用哈希法 在使用两层for循环的时候能做的剪枝操作很有限虽然时间复杂度是O(n^2)也是可以在leetcode上通过但是程序的执行时间依然比较长 。
而且使用哈希法 在使用两层for循环的时候能做的剪枝操作很有限虽然时间复杂度是$O(n^2)$也是可以在leetcode上通过但是程序的执行时间依然比较长 。
接下来我来介绍另一个解法:双指针法,**这道题目使用双指针法 要比哈希法高效一些**,那么来讲解一下具体实现的思路。
@ -101,7 +101,7 @@ public:
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了left 就向右移动才能让三数之和大一些直到left与right相遇为止
时间复杂度O(n^2)。
时间复杂度:$O(n^2)$
C++代码代码如下:

View File

@ -35,11 +35,11 @@
[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层for循环num[i]为确定值然后循环内有left和right下标作为双指针找到nums[i] + nums[left] + nums[right] == 0。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值依然是循环内有left和right下标作为双指针找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况三数之和的时间复杂度是O(n^2)四数之和的时间复杂度是O(n^3) 。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值依然是循环内有left和right下标作为双指针找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况三数之和的时间复杂度是$O(n^2)$,四数之和的时间复杂度是$O(n^3)$
那么一样的道理,五数之和、六数之和等等都采用这种解法。
对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力O(n^3)的解法降为O(n^2)的解法四数之和的双指针解法就是将原本暴力O(n^4)的解法降为O(n^3)的解法。
对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力$O(n^3)$的解法,降为$O(n^2)$的解法,四数之和的双指针解法就是将原本暴力$O(n^4)$的解法,降为$O(n^3)$的解法。
之前我们讲过哈希表的经典题目:[454.四数相加II](https://programmercarl.com/0454.四数相加II.html)相对于本题简单很多因为本题是要求在一个集合中找出四个数相加等于target同时四元组不能重复。
@ -47,7 +47,7 @@
我们来回顾一下,几道题目使用了双指针法。
双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下:
双指针法将时间复杂度$O(n^2)$的解法优化为 $O(n)$的解法。也就是降一个数量级,题目如下:
* [27.移除元素](https://programmercarl.com/0027.移除元素.html)
* [15.三数之和](https://programmercarl.com/0015.三数之和.html)

View File

@ -62,6 +62,7 @@ public:
}
};
```
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
@ -73,7 +74,7 @@ public:
上面的代码我第一次提交执行用时8ms打败6.5%的用户,差点吓到我了。
心想应该没有更好的方法了吧也就O(n)的时间复杂度,重复提交几次,这样了:
心想应该没有更好的方法了吧,也就$O(n)$的时间复杂度,重复提交几次,这样了:
![24.两两交换链表中的节点](https://code-thinking.cdn.bcebos.com/pics/24.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.png)
@ -85,7 +86,7 @@ public:
## 其他语言版本
C:
```
```c
/**
* Definition for singly-linked list.
* struct ListNode {

View File

@ -11,7 +11,7 @@
给你一个数组 nums 和一个值 val你需要 原地 移除所有数值等于 val 的元素并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并**原地**修改输入数组。
不要使用额外的数组空间,你必须仅使用 $O(1)$ 额外空间并**原地**修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
@ -184,8 +184,8 @@ func removeElement(nums []int, val int) int {
JavaScript:
```javascript
//时间复杂度O(n)
//空间复杂度O(1)
//时间复杂度O(n)
//空间复杂度O(1)
var removeElement = (nums, val) => {
let k = 0;
for(let i = 0;i < nums.length;i++){

View File

@ -229,9 +229,9 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
# 时间复杂度分析
其中n为文本串长度m为模式串长度因为在匹配的过程中根据前缀表不断调整匹配的位置可以看出匹配的过程是O(n)之前还要单独生成next数组时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
其中n为文本串长度m为模式串长度因为在匹配的过程中根据前缀表不断调整匹配的位置可以看出匹配的过程是$O(n)$之前还要单独生成next数组时间复杂度是$O(m)$。所以整个KMP算法的时间复杂度是$O(n+m)$的。
暴力的解法显而易见是O(n * m),所以**KMP在字符串匹配中极大的提高的搜索的效率。**
暴力的解法显而易见是$O(n × m)$,所以**KMP在字符串匹配中极大的提高的搜索的效率。**
为了和力扣题目28.实现strStr保持一致方便大家理解以下文章统称haystack为文本串, needle为模式串。

View File

@ -11,7 +11,7 @@
如果数组中不存在目标值 target返回 [-1, -1]。
进阶你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
进阶:你可以设计并实现时间复杂度为 $O(\log n)$ 的算法解决此问题吗?
示例 1

View File

@ -73,8 +73,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
效率如下:
@ -82,7 +82,7 @@ public:
### 二分法
既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
既然暴力解法的时间复杂度是$O(n)$,就要尝试一下使用二分查找法。
![35_搜索插入位置4](https://img-blog.csdnimg.cn/202012162326354.png)
@ -140,8 +140,9 @@ public:
}
};
```
* 时间复杂度O(logn)
* 时间复杂度O(1)
* 时间复杂度:$O(\log n)$
* 时间复杂度:$O(1)$
效率如下:
![35_搜索插入位置2](https://img-blog.csdnimg.cn/2020121623272877.png)
@ -183,8 +184,8 @@ public:
};
```
* 时间复杂度O(logn)
* 时间复杂度O(1)
* 时间复杂度:$O(\log n)$
* 时间复杂度:$O(1)$
## 总结

View File

@ -129,8 +129,8 @@ public:
};
```
因为每次遍历列的时候还要向两边寻找最高的列所以时间复杂度为O(n^2)。
空间复杂度为O(1)。
因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为$O(n^2)$
空间复杂度为$O(1)$
@ -778,8 +778,9 @@ int trap(int* height, int heightSize) {
return ans;
}
```
时间复杂度 O(n)
空间复杂度 O(1)
* 时间复杂度 $O(n)$
* 空间复杂度 $O(1)$
-----------------------

View File

@ -21,8 +21,9 @@
暴力解法的思路第一层for 就是设置起始位置第二层for循环遍历数组寻找最大值
时间复杂度O(n^2)
空间复杂度O(1)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$
```CPP
class Solution {
public:
@ -96,8 +97,9 @@ public:
}
};
```
时间复杂度O(n)
空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。
@ -126,8 +128,8 @@ public:
};
```
时间复杂度O(n)
空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 总结

View File

@ -79,8 +79,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 总结

View File

@ -112,8 +112,8 @@ public:
};
```
* 时间复杂度O(nlogn) ,有一个快排
* 空间复杂度O(1)我没有算result数组返回值所需容器占的空间
* 时间复杂度:$O(n\log n)$ ,有一个快排
* 空间复杂度:$O(1)$我没有算result数组返回值所需容器占的空间
## 总结

View File

@ -10,7 +10,7 @@
[力扣题目链接](https://leetcode-cn.com/problems/spiral-matrix-ii/)
给定一个正整数 n生成一个包含 1 到 n^2 所有元素且元素按顺时针顺序螺旋排列的正方形矩阵。
给定一个正整数 n生成一个包含 1 到 $n^2$ 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:

View File

@ -80,7 +80,7 @@ public:
那二叉树的节点个数就是 2^(m + n - 1) - 1。可以理解深搜的算法就是遍历了整个满二叉树其实没有遍历整个满二叉树只是近似而已
所以上面深搜代码的时间复杂度为O(2^(m + n - 1) - 1),可以看出,这是指数级别的时间复杂度,是非常大的。
所以上面深搜代码的时间复杂度为$O(2^(m + n - 1) - 1)$,可以看出,这是指数级别的时间复杂度,是非常大的。
### 动态规划
@ -142,8 +142,9 @@ public:
}
};
```
* 时间复杂度O(m * n)
* 空间复杂度O(m * n)
* 时间复杂度:$O(m × n)$
* 空间复杂度:$O(m × n)$
其实用一个一维数组也可以理解是滚动数组就可以了但是不利于理解可以优化点空间建议先理解了二维在理解一维C++代码如下:
@ -162,8 +163,9 @@ public:
}
};
```
* 时间复杂度O(m * n)
* 空间复杂度O(n)
* 时间复杂度:$O(m × n)$
* 空间复杂度:$O(n)$
### 数论方法
@ -222,8 +224,8 @@ public:
};
```
时间复杂度O(m)
空间复杂度O(1)
* 时间复杂度:$O(m)$
* 空间复杂度:$O(1)$
**计算组合问题的代码还是有难度的,特别是处理溢出的情况!**

View File

@ -152,8 +152,9 @@ public:
}
};
```
* 时间复杂度O(n * m) n m 分别为obstacleGrid 长度和宽度
* 空间复杂度O(n * m)
* 时间复杂度:$O(n × m)$n、m 分别为obstacleGrid 长度和宽度
* 空间复杂度:$O(n × m)$
## 总结

View File

@ -123,8 +123,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
当然依然也可以,优化一下空间复杂度,代码如下:
@ -146,8 +147,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
后面将讲解的很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化,**但我个人认为面试中能写出版本一就够了哈,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化**。

View File

@ -51,7 +51,7 @@ public:
};
```
如上代码并不能通过leetcode超时了因为时间复杂度是O(n^2)。
如上代码并不能通过leetcode超时了因为时间复杂度是$O(n^2)$
## 动态规划

View File

@ -136,8 +136,9 @@ public:
}
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$
大家应该发现了,我们分析了这么多,最后代码却如此简单!

View File

@ -46,8 +46,8 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(1)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$
当然该方法超时了。
@ -71,8 +71,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
### 动态规划
@ -155,8 +156,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
从递推公式可以看出dp[i]只是依赖于dp[i - 1]的状态。
@ -185,8 +187,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
这里能写出版本一就可以了版本二虽然原理都一样但是想直接写出版本二还是有点麻烦容易自己给自己找bug。

View File

@ -90,8 +90,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
### 动态规划
@ -116,8 +117,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 总结

View File

@ -88,8 +88,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
大家可以本题和[121. 买卖股票的最佳时机](https://programmercarl.com/0121.买卖股票的最佳时机.html)的代码几乎一样,唯一的区别在:
@ -121,8 +121,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
@ -135,7 +135,7 @@ Java
class Solution
// 实现1二维数组存储
// 可以将每天持有与否的情况分别用 dp[i][0] 和 dp[i][1] 来进行存储
// 时间复杂度O(n)空间复杂度O(n)
// 时间复杂度O(n),空间复杂度O(n)
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2]; // 创建二维数组存储状态
@ -204,7 +204,7 @@ class Solution:
Go
```go
// 买卖股票的最佳时机Ⅱ 动态规划
// 时间复杂度O(n) 空间复杂度O(n)
// 时间复杂度O(n) 空间复杂度O(n)
func maxProfit(prices []int) int {
dp := make([][]int, len(prices))
status := make([]int, len(prices) * 2)

View File

@ -146,8 +146,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度:O(n * 5)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n × 5)$
当然大家可以看到力扣官方题解里的一种优化空间写法我这里给出对应的C++版本:
@ -171,8 +171,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
大家会发现dp[2]利用的是当天的dp[1]。 但结果也是对的。

View File

@ -48,7 +48,7 @@
## 暴力方法
暴力的方法很明显就是O(n^2)的,遍历每一个加油站为起点的情况,模拟一圈。
暴力的方法很明显就是$O(n^2)$的,遍历每一个加油站为起点的情况,模拟一圈。
如果跑了一圈中途没有断油而且最后油量大于等于0说明这个起点是ok的。
@ -76,8 +76,9 @@ public:
}
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$
C++暴力解法在leetcode上提交也可以过。
@ -119,8 +120,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
**其实我不认为这种方式是贪心算法,因为没有找出局部最优,而是直接从全局最优的角度上思考问题**。
@ -173,8 +174,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
**说这种解法为贪心算法,才是是有理有据的,因为全局最优解是根据局部最优推导出来的**。

View File

@ -66,8 +66,8 @@ public:
};
```
* 时间复杂度O(2^n),因为每一个单词都有两个状态,切割和不切割
* 空间复杂度O(n),算法递归系统调用栈的空间
* 时间复杂度:$O(2^n)$,因为每一个单词都有两个状态,切割和不切割
* 空间复杂度:$O(n)$,算法递归系统调用栈的空间
那么以上代码很明显要超时了,超时的数据如下:
@ -115,7 +115,7 @@ public:
};
```
这个时间复杂度其实也是O(2^n)。只不过对于上面那个超时测试用例优化效果特别明显。
这个时间复杂度其实也是:$O(2^n)$。只不过对于上面那个超时测试用例优化效果特别明显。
**这个代码就可以AC了当然回溯算法不是本题的主菜背包才是**
@ -207,8 +207,9 @@ public:
}
};
```
* 时间复杂度O(n^3)因为substr返回子串的副本是O(n)的复杂度这里的n是substring的长度
* 空间复杂度O(n)
* 时间复杂度:$O(n^3)$因为substr返回子串的副本是$O(n)$的复杂度这里的n是substring的长度
* 空间复杂度:$O(n)$
## 总结

View File

@ -36,7 +36,7 @@
一些同学会使用split库函数分隔单词然后定义一个新的string字符串最后再把单词倒序相加那么这道题题目就是一道水题了失去了它的意义。
所以这里我还是提高一下本题的难度:**不要使用辅助空间空间复杂度要求为O(1)。**
所以这里我还是提高一下本题的难度:**不要使用辅助空间,空间复杂度要求为$O(1)$。**
不能使用辅助空间之后,那么只能在原字符串上下功夫了。
@ -79,13 +79,13 @@ void removeExtraSpaces(string& s) {
逻辑很简单从前向后遍历遇到空格了就erase。
如果不仔细琢磨一下erase的时间复杂读还以为以上的代码是O(n)的时间复杂度呢。
如果不仔细琢磨一下erase的时间复杂读还以为以上的代码是$O(n)$的时间复杂度呢。
想一下真正的时间复杂度是多少一个erase本来就是O(n)的操作erase实现原理题目[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)最优的算法来移除元素也要O(n)。
想一下真正的时间复杂度是多少一个erase本来就是$O(n)$的操作erase实现原理题目[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html),最优的算法来移除元素也要$O(n)$
erase操作上面还套了一个for循环那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。
erase操作上面还套了一个for循环那么以上代码移除冗余空格的代码时间复杂度为$O(n^2)$
那么使用双指针法来去移除空格最后resize重新设置一下字符串的大小就可以做到O(n)的时间复杂度。
那么使用双指针法来去移除空格最后resize重新设置一下字符串的大小就可以做到$O(n)$的时间复杂度。
如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html)是如何移除元素的。

View File

@ -12,7 +12,7 @@
进阶:
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
你可以使用空间复杂度为 $O(1)$ 的 原地 算法解决这个问题吗?
示例 1:
@ -41,7 +41,7 @@
本题其实和[字符串剑指Offer58-II.左旋转字符串](https://programmercarl.com/剑指Offer58-II.左旋转字符串.html)就非常像了剑指offer上左旋转本题是右旋转。
注意题目要求是**要求使用空间复杂度为 O(1) 的 原地 算法**
注意题目要求是**要求使用空间复杂度为 $O(1)$ 的 原地 算法**
那么我来提供一种旋转的方式哈。

View File

@ -20,7 +20,7 @@
## 暴力解法
这道题目暴力解法当然是 两个for循环然后不断的寻找符合条件的子序列时间复杂度很明显是O(n^2)
这道题目暴力解法当然是 两个for循环然后不断的寻找符合条件的子序列时间复杂度很明显是$O(n^2)$
代码如下:
@ -80,7 +80,7 @@ public:
![leetcode_209](https://img-blog.csdnimg.cn/20210312160441942.png)
可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。**
可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将$O(n^2)$的暴力解法降为$O(n)$。**
C++代码如下:
@ -110,9 +110,9 @@ public:
时间复杂度:$O(n)$
空间复杂度:$O(1)$
**一些录友会疑惑为什么时间复杂度是O(n)**。
**一些录友会疑惑为什么时间复杂度是$O(n)$**。
不要以为for里放一个while就以为是$O(n^2)$啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是2 * n 也就是$O(n)$。
不要以为for里放一个while就以为是$O(n^2)$啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是 2 × n 也就是$O(n)$。
## 相关题目推荐

View File

@ -105,8 +105,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(logn),算上了递归系统栈占用的空间
* 时间复杂度:$O(n)$
* 空间复杂度:$O(\log n)$,算上了递归系统栈占用的空间
**网上基本都是这个精简的代码版本,其实不建议大家照着这个来写,代码确实精简,但隐藏了一些内容,连遍历的顺序都看不出来,所以初学者建议学习版本一的代码,稳稳的打基础**。
@ -138,8 +138,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 完全二叉树
@ -185,8 +185,8 @@ public:
};
```
* 时间复杂度:O(logn * logn)
* 空间复杂度O(logn)
* 时间复杂度:$O(\log n × \log n)$
* 空间复杂度:$O(\log n)$
# 其他语言版本

View File

@ -36,7 +36,7 @@
难点是如何求一个区间里的最大值呢? (这好像是废话),暴力一下不就得了。
暴力方法,遍历一遍的过程中每次从窗口中在找到最大的数值,这样很明显是O(n * k)的算法。
暴力方法,遍历一遍的过程中每次从窗口中在找到最大的数值,这样很明显是$O(n × k)$的算法。
有的同学可能会想用一个大顶堆优先级队列来存放这个窗口里的k个数字这样就可以知道最大的最大值是多少了 **但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。**
@ -183,13 +183,13 @@ public:
};
```
在来看一下时间复杂度,使用单调队列的时间复杂度是 O(n)。
在来看一下时间复杂度,使用单调队列的时间复杂度是 $O(n)$
有的同学可能想了,在队列中 push元素的过程中还有pop操作呢感觉不是纯粹的O(n)。
有的同学可能想了,在队列中 push元素的过程中还有pop操作呢感觉不是纯粹的$O(n)$
其实大家可以自己观察一下单调队列的实现nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,所以整体的复杂度还是 O(n)。
其实大家可以自己观察一下单调队列的实现nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,所以整体的复杂度还是 $O(n)$
空间复杂度因为我们定义一个辅助队列所以是O(k)。
空间复杂度因为我们定义一个辅助队列,所以是$O(k)$
# 扩展

View File

@ -27,7 +27,7 @@
## 思路
先看暴力的解法两层for循环同时还要记录字符是否重复出现很明显时间复杂度是 O(n^2)。
先看暴力的解法两层for循环同时还要记录字符是否重复出现很明显时间复杂度是 $O(n^2)$
暴力的方法这里就不做介绍了,直接看一下有没有更优的方式。
@ -55,7 +55,7 @@
最后如果record数组所有元素都为零0说明字符串s和t是字母异位词return true。
时间复杂度为O(n)空间上因为定义是的一个常量大小的辅助数组所以空间复杂度为O(1)。
时间复杂度为$O(n)$,空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为$O(1)$
C++ 代码如下:

View File

@ -30,7 +30,7 @@
好了,我们说一说双指针法,大家如果对双指针还不熟悉,可以看我的这篇总结[双指针法:总结篇!](https://programmercarl.com/双指针总结.html)。
双指针法在数组移除元素中可以达到O(n)的时间复杂度,在[27.移除元素](https://programmercarl.com/0027.移除元素.html)里已经详细讲解了,那么本题和移除元素其实是一个套路。
双指针法在数组移除元素中,可以达到$O(n)$的时间复杂度,在[27.移除元素](https://programmercarl.com/0027.移除元素.html)里已经详细讲解了,那么本题和移除元素其实是一个套路。
**相当于对整个数组移除元素0然后slowIndex之后都是移除元素0的冗余元素把这些元素都赋值为0就可以了**。

View File

@ -147,8 +147,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
当然空间复杂度可以优化定义一个dp[2][4]大小的数组就可以了,就保存前一天的当前的状态,感兴趣的同学可以自己去写一写,思路是一样的。

View File

@ -50,8 +50,8 @@ public:
};
```
* 时间复杂度:O(n^2) 这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
* 空间复杂度:O(logn) 算上递推系统栈的空间
* 时间复杂度:$O(n^2)$这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
* 空间复杂度:$O(\log n)$算上递推系统栈的空间
当然以上代码超时了,这个递归的过程中其实是有重复计算了。
@ -83,8 +83,9 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(logn) 算上递推系统栈的空间
* 时间复杂度:$O(n)$
* 空间复杂度:$O(\log n)$,算上递推系统栈的空间
### 动态规划
@ -197,8 +198,8 @@ public:
}
};
```
* 时间复杂度:O(n) 每个节点只遍历了一次
* 空间复杂度:O(logn) 算上递推系统栈的空间
* 时间复杂度:$O(n)$每个节点只遍历了一次
* 空间复杂度:$O(\log n)$算上递推系统栈的空间
## 总结

View File

@ -120,8 +120,8 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$
### 贪心
@ -148,8 +148,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
## 总结

View File

@ -14,7 +14,7 @@
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 $O(1)$ 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

View File

@ -25,7 +25,7 @@
提示:
* 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
* 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
* 你的算法的时间复杂度必须优于 $O(n \log n)$ , n 是数组的大小。
* 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
* 你可以按任意顺序返回答案。

View File

@ -28,7 +28,7 @@
注意题目特意说明:**输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序**
这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。
这道题用暴力的解法时间复杂度是$O(n^2)$,那来看看使用哈希法进一步优化。
那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://programmercarl.com/0242.有效的字母异位词.html)

View File

@ -87,8 +87,9 @@ public:
}
};
```
时间复杂度O(n)
空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
## 思路2动态规划
@ -137,9 +138,8 @@ public:
};
```
时间复杂度O(n^2)
空间复杂度O(n)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$
**进阶**
@ -149,9 +149,9 @@ public:
* 每次更新`dp[i][1]`,则在`tree2`的`nums[i]`位置值更新为`dp[i][1]`
* 则dp转移方程中就没有必要j从0遍历到i-1可以直接在线段树中查询指定区间的值即可。
时间复杂度O(nlogn)
时间复杂度$O(n\log n)$
空间复杂度O(n)
空间复杂度$O(n)$
## 总结

View File

@ -31,7 +31,7 @@
## 思路
这道题可以用双指针的思路来实现时间复杂度就是O(n)
(这道题可以用双指针的思路来实现,时间复杂度就是$O(n)$
这道题应该算是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。
@ -122,8 +122,8 @@ public:
};
```
* 时间复杂度:O(n * m)
* 空间复杂度:O(n * m)
* 时间复杂度:$O(n × m)$
* 空间复杂度:$O(n × m)$
## 总结

View File

@ -116,12 +116,12 @@ public:
}
};
```
* 时间复杂度O(nlogn + n^2)
* 空间复杂度O(n)
* 时间复杂度$O(n\log n + n^2)$
* 空间复杂度$O(n)$
但使用vector是非常费时的C++中vector可以理解是一个动态数组底层是普通数组实现的如果插入元素大于预先普通数组大小vector底部会有一个扩容的操作即申请两倍于原先普通数组的大小然后把数据拷贝到另一个更大的数组上。
所以使用vector动态数组来insert是费时的插入再拷贝的话单纯一个插入的操作就是O(n^2)了甚至可能拷贝好几次就不止O(n^2)了。
所以使用vector动态数组来insert是费时的插入再拷贝的话单纯一个插入的操作就是$O(n^2)$了,甚至可能拷贝好几次,就不止$O(n^2)$了。
改成链表之后C++代码如下:
@ -150,8 +150,8 @@ public:
};
```
* 时间复杂度O(nlogn + n^2)
* 空间复杂度O(n)
* 时间复杂度$O(n\log n + n^2)$
* 空间复杂度$O(n)$
大家可以把两个版本的代码提交一下试试,就可以发现其差别了!

View File

@ -168,8 +168,8 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)虽然dp数组大小为一个常数但是大常数
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n)$虽然dp数组大小为一个常数但是大常数
## 总结

View File

@ -92,8 +92,8 @@ public:
}
};
```
* 时间复杂度O(nlogn) ,有一个快排
* 空间复杂度O(1)
* 时间复杂度:$O(n\log n)$ ,有一个快排
* 空间复杂度:$O(1)$
大家此时会发现如此复杂的一个问题,代码实现却这么简单!

View File

@ -17,7 +17,7 @@
首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h)h 为树的高度。
说明: 要求算法时间复杂度为 $O(h)$h 为树的高度。
示例:

View File

@ -105,8 +105,8 @@ public:
};
```
* 时间复杂度O(nlogn),因为有一个快排
* 空间复杂度O(1)
* 时间复杂度$O(n\log n)$,因为有一个快排
* 空间复杂度$O(1)$
可以看出代码并不复杂。

View File

@ -221,8 +221,8 @@ public:
};
```
* 时间复杂度O(n * m)n为正数个数m为背包容量
* 空间复杂度:O(m) m为背包容量
* 时间复杂度$O(n × m)$n为正数个数m为背包容量
* 空间复杂度:$O(m)$m为背包容量
## 总结

View File

@ -68,7 +68,7 @@ public:
这种写法确实比较直观但做了很多无用操作例如修改了nums数组而且最后还要把result数组resize回去。
resize倒是不费时间是O(1)的操作但扩充nums数组相当于多了一个O(n)的操作。
resize倒是不费时间$O(1)$的操作但扩充nums数组相当于多了一个$O(n)$的操作。
其实也可以不扩充nums而是在遍历的过程中模拟走了两边nums。

View File

@ -101,8 +101,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
当然可以发现,我们只需要维护两个数值就可以了,不需要记录整个序列。
@ -126,8 +126,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
### 递归解法
@ -145,8 +145,8 @@ public:
};
```
* 时间复杂度O(2^n)
* 空间复杂度:O(n) 算上了编程语言中实现递归的系统栈所占空间
* 时间复杂度:$O(2^n)$
* 空间复杂度:$O(n)$算上了编程语言中实现递归的系统栈所占空间
这个递归的时间复杂度大家画一下树形图就知道了,如果不清晰的同学,可以看这篇:[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)

View File

@ -32,7 +32,7 @@
两层for循环遍历区间起始位置和终止位置然后判断这个区间是不是回文。
时间复杂度O(n^3)
时间复杂度:$O(n^3)$
## 动态规划
@ -171,8 +171,8 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n^2)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(n^2)$
## 双指针法
@ -212,8 +212,9 @@ public:
}
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(1)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$
## 其他语言版本

View File

@ -216,10 +216,10 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
* 时间复杂度$O(n^2)$
* 空间复杂度$O(n)$
还有O(nlogn)的解法使用树状数组今天有点忙就先不写了感兴趣的同学可以自行学习一下这里有我之前写的树状数组系列博客https://blog.csdn.net/youngyangyang04/category_871105.html (十年前的陈年老文了)
还有$O(n\log n)$的解法使用树状数组今天有点忙就先不写了感兴趣的同学可以自行学习一下这里有我之前写的树状数组系列博客https://blog.csdn.net/youngyangyang04/category_871105.html (十年前的陈年老文了)
# 其他语言版本

View File

@ -107,8 +107,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
### 贪心
@ -135,12 +135,12 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
## 总结
本题也是动规里子序列问题的经典题目但也可以用贪心来做大家也会发现贪心好像更简单一点而且空间复杂度仅是O(1)。
本题也是动规里子序列问题的经典题目,但也可以用贪心来做,大家也会发现贪心好像更简单一点,而且空间复杂度仅是$O(1)$
在动规分析中,关键是要理解和[动态规划300.最长递增子序列](https://programmercarl.com/0300.最长上升子序列.html)的区别。

View File

@ -84,8 +84,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
从代码中可以看出对情况一的操作,因为如果还在收获利润的区间里,表示并不是真正的卖出,而计算利润每次都要减去手续费,**所以要让minPrice = prices[i] - fee;,这样在明天收获利润的时候,才不会多减一次手续费!**
@ -117,8 +117,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
当然可以对空间经行优化,因为当前状态只是依赖前一个状态。
@ -140,8 +140,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
## 总结

View File

@ -37,8 +37,9 @@
在讲解贪心专题的时候,我们已经讲过本题了[贪心算法:买卖股票的最佳时机含手续费](https://programmercarl.com/0714.买卖股票的最佳时机含手续费.html)
使用贪心算法,的性能是:
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
那么我们再来看看是使用动规的方法如何解题。
@ -86,8 +87,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 其他语言版本

View File

@ -111,8 +111,8 @@ public:
};
```
* 时间复杂度O(n * m) n 为A长度m为B长度
* 空间复杂度O(n * m)
* 时间复杂度$O(n × m)$n 为A长度m为B长度
* 空间复杂度$O(n × m)$
## 滚动数组
@ -145,8 +145,8 @@ public:
};
```
* 时间复杂度O(n * m) n 为A长度m为B长度
* 空间复杂度O(m)
* 时间复杂度$O(n × m)$n 为A长度m为B长度
* 空间复杂度$O(m)$
## 其他语言版本

View File

@ -54,8 +54,8 @@ public:
}
};
```
* 时间复杂度:O(n * m) m为n的数字长度
* 空间复杂度O(1)
* 时间复杂度:$O(n × m)$ m为n的数字长度
* 空间复杂度:$O(1)$
## 贪心算法
@ -108,8 +108,8 @@ public:
```
* 时间复杂度:O(n) n 为数字长度
* 空间复杂度:O(n) 需要一个字符串,转化为字符串操作更方便
* 时间复杂度:$O(n)$n 为数字长度
* 空间复杂度:$O(n)$需要一个字符串,转化为字符串操作更方便
## 总结

View File

@ -18,7 +18,7 @@
## 思路
首先想到的当然是暴力解法两层for循环把至少需要等待的天数就搜出来了。时间复杂度是O(n^2)
首先想到的当然是暴力解法两层for循环把至少需要等待的天数就搜出来了。时间复杂度是$O(n^2)$
那么接下来在来看看使用单调栈的解法。
@ -26,13 +26,13 @@
**通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了**。
时间复杂度为O(n)。
时间复杂度为$O(n)$
例如本题其实就是找找到一个元素右边第一个比自己大的元素。
此时就应该想到用单调栈了。
那么单调栈的原理是什么呢为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢?
那么单调栈的原理是什么呢?为什么时间复杂度是$O(n)$就可以找到每一个元素的右边第一个比它大的元素位置呢?
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素的元素,优点是只需要遍历一次。
@ -165,8 +165,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。

View File

@ -113,8 +113,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
还可以优化空间复杂度因为dp[i]就是由前两位推出来的那么也不用dp数组了C++代码如下:
@ -136,8 +136,8 @@ public:
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
**当然我不建议这么写,能写出版本一就可以了,直观简洁!**

View File

@ -68,8 +68,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度:O(1) 使用的hash数组是固定大小
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$使用的hash数组是固定大小
## 总结

View File

@ -36,7 +36,7 @@
# 思路
本文将给出 空间复杂度O(n)的栈模拟方法 以及空间复杂度是O(1)的双指针方法。
本文将给出 空间复杂度$O(n)$的栈模拟方法 以及空间复杂度是$O(1)$的双指针方法。
## 普通方法(使用栈的思路)
@ -71,8 +71,8 @@ public:
}
};
```
* 时间复杂度:O(n + m) n为S的长度m为T的长度 也可以理解是O(n)的时间复杂度
* 空间复杂度:O(n + m)
* 时间复杂度:$O(n + m)$n为S的长度m为T的长度 ,也可以理解是$O(n)$的时间复杂度
* 空间复杂度:$O(n + m)$
当然以上代码大家可以发现有重复的逻辑处理S处理T可以把这块公共逻辑抽离出来代码精简如下
@ -96,12 +96,13 @@ public:
};
```
性能依然是:
* 时间复杂度:O(n + m)
* 空间复杂度:O(n + m)
* 时间复杂度:$O(n + m)$
* 空间复杂度:$O(n + m)$
## 优化方法(从后向前双指针)
当然还可以有使用 O(1) 的空间复杂度来解决该问题。
当然还可以有使用 $O(1)$ 的空间复杂度来解决该问题。
同时从后向前遍历S和Ti初始为S末尾j初始为T末尾记录#的数量,模拟消除的操作,如果#用完了就开始比较S[i]和S[j]。
@ -150,8 +151,8 @@ public:
};
```
* 时间复杂度O(n + m)
* 空间复杂度O(1)
* 时间复杂度:$O(n + m)$
* 空间复杂度:$O(1)$
# 其他语言版本

View File

@ -26,11 +26,11 @@
## 思路
这道题目直接的想法可能是两层for循环再加上used数组表示使用过的元素。这样的的时间复杂度是O(n^2)。
这道题目直接的想法可能是两层for循环再加上used数组表示使用过的元素。这样的的时间复杂度是$O(n^2)$
### 方法一
其实这道题可以用很朴实的方法时间复杂度就就是O(n)了C++代码如下:
其实这道题可以用很朴实的方法,时间复杂度就就是$O(n)$C++代码如下:
```CPP
class Solution {
@ -57,8 +57,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
### 方法二
@ -86,8 +86,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度$O(n)$
* 空间复杂度$O(n)$
### 方法三
@ -109,10 +109,10 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$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的关系
## 其他语言版本

View File

@ -90,8 +90,8 @@ public:
```
时间复杂度O(n)
空间复杂度O(1)
时间复杂度:$O(n)$
空间复杂度:$O(1)$
# 其他语言版本

View File

@ -40,7 +40,7 @@ public:
};
```
这个时间复杂度是 O(n + nlogn) 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlogn)。
这个时间复杂度是 $O(n + n\log n)$ 可以说是$O(n\log n)$的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 $O(n + n\log n)$
## 双指针法
@ -83,7 +83,7 @@ public:
};
```
此时的时间复杂度为O(n)相对于暴力排序的解法O(n + nlogn)还是提升不少的。
此时的时间复杂度为$O(n)$,相对于暴力排序的解法$O(n + n\log n)$还是提升不少的。
**这里还是说一下大家不必太在意leetcode上执行用时打败多少多少用户这个就是一个玩具非常不准确。**

View File

@ -40,7 +40,7 @@ words[i] 由小写英文字母组成
这道题目一眼看上去,就是用哈希法,**“小写字符”,“出现频率”, 这些关键字都是为哈希法量身定做的啊**
首先可以想到的是暴力解法一个字符串一个字符串去搜时间复杂度是O(n^m)n是字符串长度m是有几个字符串。
首先可以想到的是暴力解法,一个字符串一个字符串去搜,时间复杂度是$O(n^m)$n是字符串长度m是有几个字符串。
可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。

View File

@ -136,8 +136,8 @@ public:
```
* 时间复杂度:O(m * n) , m是石头总重量准确的说是总重量的一半n为石头块数
* 空间复杂度O(m)
* 时间复杂度:$O(m × n)$ , m是石头总重量准确的说是总重量的一半n为石头块数
* 空间复杂度:$O(m)$
## 总结

View File

@ -41,7 +41,7 @@
# 思路
两层for循环暴力查找时间复杂度明显为O(n^2)。
两层for循环暴力查找时间复杂度明显为$O(n^2)$
那么我们来看一下如何优化。
@ -110,7 +110,7 @@ public:
};
```
可以排序之后加哈希时间复杂度为O(nlogn)
可以排序之后加哈希,时间复杂度为$O(n\log n)$
# 其他语言版本

View File

@ -3,7 +3,7 @@
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20210924105952.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 程序提交之后为什么会超时O(n)的算法会超时n究竟是多大
# 程序提交之后为什么会超时?$O(n)$的算法会超时n究竟是多大
一些同学可能对计算机运行的速度还没有概念就是感觉计算机运行速度应该会很快那么在leetcode上做算法题目的时候为什么会超时呢
@ -18,9 +18,9 @@
也就是说程序运行的时间超过了规定的时间一般OJonline judge的超时时间就是1s也就是用例数据输入后最多要1s内得到结果暂时还不清楚leetcode的判题规则下文为了方便讲解暂定超时时间就是1s。
如果写出了一个O(n)的算法 其实可以估算出来n是多大的时候算法的执行时间就会超过1s了。
如果写出了一个$O(n)$的算法 其实可以估算出来n是多大的时候算法的执行时间就会超过1s了。
如果n的规模已经足够让O(n)的算法运行时间超过了1s就应该考虑log(n)的解法了。
如果n的规模已经足够让$O(n)$的算法运行时间超过了1s就应该考虑log(n)的解法了。
# 从硬件配置看计算机的性能
@ -63,7 +63,7 @@
测试硬件2015年MacProCPU配置2.7 GHz Dual-Core Intel Core i5
实现三个函数,时间复杂度分别是 O(n) , O(n^2), O(nlogn),使用加法运算来统一测试。
实现三个函数,时间复杂度分别是 $O(n)$ , $O(n^2)$, $O(n\log n)$,使用加法运算来统一测试。
```CPP
// O(n)
@ -128,19 +128,19 @@ int main() {
![程序超时2](https://img-blog.csdnimg.cn/20200729200018460.png)
O(n)的算法1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下O(n^2) 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。
O(n)的算法1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下$O(n^2)$ 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。
![程序超时3](https://img-blog.csdnimg.cn/2020072919590970.png)
O(n^2)的算法1s内大概计算机可以运行 22500次计算验证了刚刚的推测。
在推测一下O(nlogn)的话, 1s可以处理的数据规模是什么呢
在推测一下$O(n\log n)$的话, 1s可以处理的数据规模是什么呢
理论上应该是比 O(n)少一个数量级因为logn的复杂度 其实是很快,看一下实验数据。
理论上应该是比 $O(n)$少一个数量级,因为$\log n$的复杂度 其实是很快,看一下实验数据。
![程序超时4](https://img-blog.csdnimg.cn/20200729195729407.png)
O(nlogn)的算法1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。
$O(n \logn)$的算法1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。
这是在我个人PC上测出来的数据不能说是十分精确但数量级是差不多的大家也可以在自己的计算机上测一下。
@ -148,7 +148,7 @@ O(nlogn)的算法1s内大概计算机可以运行 2 * (10^7)次计算,符
![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png)
至于O(logn) 和O(n^3) 等等这些时间复杂度在1s内可以处理的多大的数据规模大家可以自己写一写代码去测一下了。
至于$O(\log n)$和$O(n^3)$ 等等这些时间复杂度在1s内可以处理的多大的数据规模大家可以自己写一写代码去测一下了。
# 完整测试代码
@ -209,7 +209,7 @@ int main() {
# 总结
本文详细分析了在leetcode上做题程序为什么会有超时以及从硬件配置上大体知道CPU的执行速度然后亲自做一个实验来看看O(n)的算法跑一秒钟这个n究竟是做大最后给出不同时间复杂度一秒内可以运算出来的n的大小。
本文详细分析了在leetcode上做题程序为什么会有超时以及从硬件配置上大体知道CPU的执行速度然后亲自做一个实验来看看$O(n)$的算法跑一秒钟这个n究竟是做大最后给出不同时间复杂度一秒内可以运算出来的n的大小。
建议录友们也都自己做一做实验,测一测,看看是不是和我的测出来的结果差不多。

View File

@ -16,21 +16,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(nlogn)但是当数据已经有序情况下快速排序的时间复杂度是O(n^2) 的,**所以严格从大O的定义来讲快速排序的时间复杂度应该是O(n^2)**。
同样的同理再看一下快速排序,都知道快速排序是$O(n\log n)$,但是当数据已经有序情况下,快速排序的时间复杂度是$O(n^2)$ 的,**所以严格从大O的定义来讲快速排序的时间复杂度应该是$O(n^2)$**。
**但是我们依然说快速排序是O(nlogn)的时间复杂度这个就是业内的一个默认规定这里说的O代表的就是一般情况而不是严格的上界**。如图所示:
**但是我们依然说快速排序是$O(n\log n)$的时间复杂度这个就是业内的一个默认规定这里说的O代表的就是一般情况而不是严格的上界**。如图所示:
![时间复杂度4一般情况下的时间复杂度](https://img-blog.csdnimg.cn/20200728185745611.png)
我们主要关心的还是一般情况下的数据形式。
@ -44,11 +44,11 @@
![时间复杂度,不同数据规模的差异](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就是数据量级突破一个点且数据量级非常大的情况下所表现出的时间复杂度这个数据量也就是常数项系数已经不起决定性作用的数据量**。
@ -56,13 +56,13 @@
**所以我们说的时间复杂度都是省略常数项系数的,是因为一般情况下都是默认数据规模足够的大,基于这样的事实,给出的算法时间复杂的的一个排行如下所示**
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n^2)平方阶 < O(n^3)(立方阶) < O(2^n) (指数阶)
O(1) 常数阶 < $O(\log n)$ 对数阶 < $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)
@ -88,19 +88,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(logn)中的log是以什么为底
## $O(\log n)$中的log是以什么为底
平时说这个算法的时间复杂度是logn的那么一定是log 以2为底n的对数么
@ -123,21 +123,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(nlogn)依然要考虑字符串的长度是m那么快速排序每次的比较都要有m次的字符比较的操作就是O(m * n * logn)
那看看这种算法的时间复杂度,快速排序时间复杂度为$O(n\log n)$依然要考虑字符串的长度是m那么快速排序每次的比较都要有m次的字符比较的操作就是$O(m × n × \log n)$
之后还要遍历一遍这n个字符串找出两个相同的字符串别忘了遍历的时候依然要比较字符串所以总共的时间复杂度是 O(m * n * logn + n * m)
之后还要遍历一遍这n个字符串找出两个相同的字符串别忘了遍历的时候依然要比较字符串所以总共的时间复杂度是 $O(m × n × \log n + n × m)$
我们对O(m * n * logn + n * m) 进行简化操作把m * n提取出来变成 O(m * n * (logn + 1)),再省略常数项最后的时间复杂度是 O(m * n * logn)
我们对$O(m × n × \log n + n × m)$进行简化操作,把$m × n$提取出来变成$O(m × n × (\log n + 1)$),再省略常数项最后的时间复杂度是$O(m × n × \log n)$
最后很明显O(m * n * logn) 要优于O(m * n * n)
最后很明显$O(m × n × \log n)$ 要优于$O(m × n × n)$
所以先把字符串集合排序再遍历一遍找到两个相同字符串的方法要比直接暴力枚举的方式更快。

View File

@ -20,9 +20,9 @@
也就是说程序运行的时间超过了规定的时间一般OJonline judge的超时时间就是1s也就是用例数据输入后最多要1s内得到结果暂时还不清楚leetcode的判题规则下文为了方便讲解暂定超时时间就是1s。
如果写出了一个O(n)的算法 其实可以估算出来n是多大的时候算法的执行时间就会超过1s了。
如果写出了一个$O(n)$的算法 其实可以估算出来n是多大的时候算法的执行时间就会超过1s了。
如果n的规模已经足够让O(n)的算法运行时间超过了1s就应该考虑log(n)的解法了。
如果n的规模已经足够让$O(n)$的算法运行时间超过了1s就应该考虑log(n)的解法了。
# 从硬件配置看计算机的性能
@ -65,7 +65,7 @@
测试硬件2015年MacProCPU配置2.7 GHz Dual-Core Intel Core i5
实现三个函数,时间复杂度分别是 O(n) , O(n^2), O(nlogn),使用加法运算来统一测试。
实现三个函数,时间复杂度分别是 $O(n)$ , $O(n^2)$, $O(n\log n)$,使用加法运算来统一测试。
```CPP
// O(n)
@ -130,19 +130,19 @@ int main() {
![程序超时2](https://img-blog.csdnimg.cn/20200729200018460.png)
O(n)的算法1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下O(n^2) 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。
O(n)的算法1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下$O(n^2)$ 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。
![程序超时3](https://img-blog.csdnimg.cn/2020072919590970.png)
O(n^2)的算法1s内大概计算机可以运行 22500次计算验证了刚刚的推测。
在推测一下O(nlogn)的话, 1s可以处理的数据规模是什么呢
在推测一下$O(n\log n)$的话, 1s可以处理的数据规模是什么呢
理论上应该是比 O(n)少一个数量级因为logn的复杂度 其实是很快,看一下实验数据。
理论上应该是比 $O(n)$少一个数量级,因为$\log n$的复杂度 其实是很快,看一下实验数据。
![程序超时4](https://img-blog.csdnimg.cn/20200729195729407.png)
O(nlogn)的算法1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。
$O(n\log n)$的算法1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。
这是在我个人PC上测出来的数据不能说是十分精确但数量级是差不多的大家也可以在自己的计算机上测一下。
@ -150,7 +150,7 @@ O(nlogn)的算法1s内大概计算机可以运行 2 * (10^7)次计算,符
![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png)
至于O(logn) 和O(n^3) 等等这些时间复杂度在1s内可以处理的多大的数据规模大家可以自己写一写代码去测一下了。
至于 $O(\log n)$ $O(n^3)$ 等等这些时间复杂度在1s内可以处理的多大的数据规模大家可以自己写一写代码去测一下了。
# 完整测试代码
@ -211,7 +211,7 @@ int main() {
# 总结
本文详细分析了在leetcode上做题程序为什么会有超时以及从硬件配置上大体知道CPU的执行速度然后亲自做一个实验来看看O(n)的算法跑一秒钟这个n究竟是做大最后给出不同时间复杂度一秒内可以运算出来的n的大小。
本文详细分析了在leetcode上做题程序为什么会有超时以及从硬件配置上大体知道CPU的执行速度然后亲自做一个实验来看看$O(n)$的算法跑一秒钟这个n究竟是做大最后给出不同时间复杂度一秒内可以运算出来的n的大小。
建议录友们也都自己做一做实验,测一测,看看是不是和我的测出来的结果差不多。

View File

@ -15,7 +15,7 @@
* 什么是大O
* 不同数据规模的差异
* 复杂表达式的化简
* O(logn)中的log是以什么为底
* $O(\log n)$中的log是以什么为底
* 举一个例子
@ -29,21 +29,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(nlogn)但是当数据已经有序情况下快速排序的时间复杂度是O(n^2) 的,**所以严格从大O的定义来讲快速排序的时间复杂度应该是O(n^2)**。
同样的同理再看一下快速排序,都知道快速排序是$O(n\log n)$,但是当数据已经有序情况下,快速排序的时间复杂度是$O(n^2)$ 的,**所以严格从大O的定义来讲快速排序的时间复杂度应该是$O(n^2)$**。
**但是我们依然说快速排序是O(nlogn)的时间复杂度这个就是业内的一个默认规定这里说的O代表的就是一般情况而不是严格的上界**。如图所示:
**但是我们依然说快速排序是$O(n\log n)$的时间复杂度这个就是业内的一个默认规定这里说的O代表的就是一般情况而不是严格的上界**。如图所示:
![时间复杂度4一般情况下的时间复杂度](https://img-blog.csdnimg.cn/20200728185745611.png)
我们主要关心的还是一般情况下的数据形式。
@ -57,11 +57,11 @@
![时间复杂度,不同数据规模的差异](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就是数据量级突破一个点且数据量级非常大的情况下所表现出的时间复杂度这个数据量也就是常数项系数已经不起决定性作用的数据量**。
@ -69,13 +69,13 @@
**所以我们说的时间复杂度都是省略常数项系数的,是因为一般情况下都是默认数据规模足够的大,基于这样的事实,给出的算法时间复杂的的一个排行如下所示**
O(1)常数阶 < O(logn)对数阶 < O(n)线性阶 < O(n^2)平方阶 < O(n^3)(立方阶) < O(2^n) (指数阶)
O(1)常数阶 < $O(\log n)$对数阶 < $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)
@ -101,19 +101,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(logn)中的log是以什么为底
## $O(\log n)$中的log是以什么为底
平时说这个算法的时间复杂度是logn的那么一定是log 以2为底n的对数么
@ -136,21 +136,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(nlogn)依然要考虑字符串的长度是m那么快速排序每次的比较都要有m次的字符比较的操作就是O(m * n * logn)
那看看这种算法的时间复杂度,快速排序时间复杂度为$O(n\log n)$依然要考虑字符串的长度是m那么快速排序每次的比较都要有m次的字符比较的操作就是$O(m × n × \log n)$
之后还要遍历一遍这n个字符串找出两个相同的字符串别忘了遍历的时候依然要比较字符串所以总共的时间复杂度是 O(m * n * logn + n * m)
之后还要遍历一遍这n个字符串找出两个相同的字符串别忘了遍历的时候依然要比较字符串所以总共的时间复杂度是 $O(m × n × \log n + n × m)$
我们对O(m * n * logn + n * m) 进行简化操作把m * n提取出来变成 O(m * n * (logn + 1)),再省略常数项最后的时间复杂度是 O(m * n * logn)
我们对$O(m × n × \log n + n × m)$ 进行简化操作,把$m × n$提取出来变成 $O(m × n × (\log n + 1)$),再省略常数项最后的时间复杂度是 $O(m × n × \log n)$
最后很明显O(m * n * logn) 要优于O(m * n * n)
最后很明显$O(m × n × \log n)$ 要优于$O(m × n × n)$
所以先把字符串集合排序再遍历一遍找到两个相同字符串的方法要比直接暴力枚举的方式更快。

View File

@ -11,14 +11,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来表示。利用程序的空间复杂度可以对程序运行中需要多少内存有个预先估计。
@ -40,7 +40,7 @@
同样在工程实践中,计算机的内存空间也不是无限的,需要工程师对软件运行时所使用的内存有一个大体评估,这都需要用到算法空间复杂度的分析。
来看一下例子什么时候的空间复杂度是O(1)呢C++代码如下:
来看一下例子,什么时候的空间复杂度是$O(1)$C++代码如下:
```CPP
int j = 0;
@ -49,11 +49,11 @@ 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++) {
@ -61,9 +61,9 @@ for (int i = 0; i < n; i++) {
}
```
我们定义了一个数组出来这个数组占用的大小为n虽然有一个for循环但没有再分配新的空间因此这段代码的空间复杂度主要看第一行即可随着n的增大开辟的内存大小呈线性增长即 O(n)。
我们定义了一个数组出来这个数组占用的大小为n虽然有一个for循环但没有再分配新的空间因此这段代码的空间复杂度主要看第一行即可随着n的增大开辟的内存大小呈线性增长$O(n)$
其他的 O(n^2) O(n^3) 我想大家应该都可以以此例举出来了,**那么思考一下 什么时候空间复杂度是 O(logn)呢?**
其他的 $O(n^2)$ $O(n^3)$ 我想大家应该都可以以此例举出来了,**那么思考一下 什么时候空间复杂度是 $O(\log n)$呢?**
空间复杂度是logn的情况确实有些特殊其实是在**递归的时候会出现空间复杂度为logn的情况**。

View File

@ -34,7 +34,7 @@ int fibonacci(int i) {
在讲解递归时间复杂度的时候,我们提到了递归算法的时间复杂度本质上是要看: **递归的次数 * 每次递归的时间复杂度**
可以看出上面的代码每次递归都是O(1)的操作。再来看递归了多少次这里将i为5作为输入的递归过程 抽象成一颗递归树,如图:
可以看出上面的代码每次递归都是$O(1)$的操作。再来看递归了多少次这里将i为5作为输入的递归过程 抽象成一颗递归树,如图:
![递归空间复杂度分析](https://img-blog.csdnimg.cn/20210305093200104.png)
@ -44,7 +44,7 @@ int fibonacci(int i) {
我们之前也有说到一棵深度按根节点深度为1为k的二叉树最多可以有 2^k - 1 个节点。
所以该递归算法的时间复杂度为 O(2^n) 这个复杂度是非常大的随着n的增大耗时是指数上升的。
所以该递归算法的时间复杂度为$O(2^n)$这个复杂度是非常大的随着n的增大耗时是指数上升的。
来做一个实验,大家可以有一个直观的感受。
@ -93,7 +93,7 @@ int main()
* n = 40耗时837 ms
* n = 50耗时110306 ms
可以看出O(2^n)这种指数级别的复杂度是非常大的。
可以看出,$O(2^n)$这种指数级别的复杂度是非常大的。
所以这种求斐波那契数的算法看似简洁,其实时间复杂度非常高,一般不推荐这样来实现斐波那契。
@ -127,14 +127,14 @@ int fibonacci(int first, int second, int n) {
这里相当于用first和second来记录当前相加的两个数值此时就不用两次递归了。
因为每次递归的时候n减1即只是递归了n次所以时间复杂度是 O(n)。
因为每次递归的时候n减1即只是递归了n次所以时间复杂度是 $O(n)$
同理递归的深度依然是n每次递归所需的空间也是常数所以空间复杂度依然是O(n)。
同理递归的深度依然是n每次递归所需的空间也是常数所以空间复杂度依然是$O(n)$
代码(版本二)的复杂度如下:
* 时间复杂度: O(n)
* 空间复杂度: O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
此时再来测一下耗时情况验证一下:
@ -198,7 +198,7 @@ int main()
因为每次递归所需的空间都被压到调用栈里(这是内存管理里面的数据结构,和算法里的栈原理是一样的),一次递归结束,这个栈就是就是把本次递归的数据弹出去。所以这个栈最大的长度就是递归的深度。
此时可以分析这段递归的空间复杂度从代码中可以看出每次递归所需要的空间大小都是一样的所以每次递归中需要的空间是一个常量并不会随着n的变化而变化每次递归的空间复杂度就是O(1)。
此时可以分析这段递归的空间复杂度从代码中可以看出每次递归所需要的空间大小都是一样的所以每次递归中需要的空间是一个常量并不会随着n的变化而变化每次递归的空间复杂度就是$O(1)$
在看递归的深度是多少呢?如图所示:
@ -206,7 +206,7 @@ int main()
递归第n个斐波那契数的话递归调用栈的深度就是n。
那么每次递归的空间复杂度是O(1) 调用栈深度为n所以这段递归代码的空间复杂度就是O(n)。
那么每次递归的空间复杂度是$O(1)$ 调用栈深度为n所以这段递归代码的空间复杂度就是$O(n)$
```CPP
int fibonacci(int i) {
@ -241,24 +241,24 @@ int binary_search( int arr[], int l, int r, int x) {
}
```
都知道二分查找的时间复杂度是O(logn),那么递归二分查找的空间复杂度是多少呢?
都知道二分查找的时间复杂度是$O(\log n)$,那么递归二分查找的空间复杂度是多少呢?
我们依然看 **每次递归的空间复杂度和递归的深度**
每次递归的空间复杂度可以看出主要就是参数里传入的这个arr数组但需要注意的是在C/C++中函数传递数组参数,不是整个数组拷贝一份传入函数而是传入的数组首元素地址。
**也就是说每一层递归都是公用一块数组地址空间的**,所以 每次递归的空间复杂度是常数即O(1)。
**也就是说每一层递归都是公用一块数组地址空间的**,所以 每次递归的空间复杂度是常数即:$O(1)$
再来看递归的深度二分查找的递归深度是logn ,递归深度就是调用栈的长度,那么这段代码的空间复杂度为 1 * logn = O(logn)。
再来看递归的深度二分查找的递归深度是logn ,递归深度就是调用栈的长度,那么这段代码的空间复杂度为 $1 * logn = O(logn)$
大家要注意自己所用的语言在传递函数参数的时是拷贝整个数值还是拷贝地址如果是拷贝整个数值那么该二分法的空间复杂度就是O(nlogn)。
大家要注意自己所用的语言在传递函数参数的时,是拷贝整个数值还是拷贝地址,如果是拷贝整个数值那么该二分法的空间复杂度就是$O(n\log n)$
## 总结
本章我们详细分析了递归实现的求斐波那契和二分法的空间复杂度,同时也对时间复杂度做了分析。
特别是两种递归实现的求斐波那契数列其时间复杂度截然不容我们还做了实验验证了时间复杂度为O(2^n)是非常耗时的。
特别是两种递归实现的求斐波那契数列,其时间复杂度截然不容,我们还做了实验,验证了时间复杂度为$O(2^n)$是非常耗时的。
通过本篇大家应该对递归算法的时间复杂度和空间复杂度有更加深刻的理解了。

View File

@ -15,13 +15,13 @@
相信很多同学对递归算法的时间复杂度都很模糊,那么这篇来给大家通透的讲一讲。
**同一道题目同样使用递归算法有的同学会写出了O(n)的代码有的同学就写出了O(logn)的代码**。
**同一道题目,同样使用递归算法,有的同学会写出了$O(n)$的代码,有的同学就写出了$O(\log n)$的代码**。
这是为什么呢?
如果对递归的时间复杂度理解的不够深入的话,就会这样!
那么我通过一道简单的面试题模拟面试的场景来带大家逐步分析递归算法的时间复杂度最后找出最优解来看看同样是递归怎么就写成了O(n)的代码。
那么我通过一道简单的面试题,模拟面试的场景,来带大家逐步分析递归算法的时间复杂度,最后找出最优解,来看看同样是递归,怎么就写成了$O(n)$的代码。
面试题求x的n次方
@ -36,7 +36,7 @@ int function1(int x, int n) {
return result;
}
```
时间复杂度为O(n),此时面试官会说,有没有效率更好的算法呢。
时间复杂度为$O(n)$,此时面试官会说,有没有效率更好的算法呢。
**如果此时没有思路,不要说:我不会,我不知道了等等**。
@ -54,11 +54,11 @@ int function2(int x, int n) {
```
面试官问:“那么这个代码的时间复杂度是多少?”。
一些同学可能一看到递归就想到了O(logn),其实并不是这样,递归算法的时间复杂度本质上是要看: **递归的次数 * 每次递归中的操作次数**
一些同学可能一看到递归就想到了$O(\log n)$,其实并不是这样,递归算法的时间复杂度本质上是要看: **递归的次数 * 每次递归中的操作次数**
那再来看代码,这里递归了几次呢?
每次n-1递归了n次时间复杂度是O(n),每次进行了一个乘法操作,乘法操作的时间复杂度一个常数项O(1),所以这份代码的时间复杂度是 n * 1 = O(n)
每次n-1递归了n次时间复杂度是$O(n)$,每次进行了一个乘法操作,乘法操作的时间复杂度一个常数项$O(1)$,所以这份代码的时间复杂度是 $n × 1 = O(n)$
这个时间复杂度就没有达到面试官的预期。于是又写出了如下的递归算法的代码:
@ -91,11 +91,11 @@ int function3(int x, int n) {
![递归求时间复杂度](https://img-blog.csdnimg.cn/20200728195531892.png)
**时间复杂度忽略掉常数项`-1`之后这个递归算法的时间复杂度依然是O(n)**。对你没看错依然是O(n)的时间复杂度!
**时间复杂度忽略掉常数项`-1`之后,这个递归算法的时间复杂度依然是$O(n)$**。对,你没看错,依然是$O(n)$的时间复杂度!
此时面试官就会说“这个递归的算法依然还是O(n)啊”, 很明显没有达到面试官的预期。
此时面试官就会说:“这个递归的算法依然还是$O(n)$啊”, 很明显没有达到面试官的预期。
那么O(logn)的递归算法应该怎么写呢?
那么$O(\log n)$的递归算法应该怎么写呢?
想一想刚刚给出的那份递归算法的代码,是不是有哪里比较冗余呢,其实有重复计算的部分。
@ -118,7 +118,7 @@ int function4(int x, int n) {
依然还是看他递归了多少次可以看到这里仅仅有一个递归调用且每次都是n/2 所以这里我们一共调用了log以2为底n的对数次。
**每次递归了做都是一次乘法操作这也是一个常数项的操作那么这个递归算法的时间复杂度才是真正的O(logn)**。
**每次递归了做都是一次乘法操作,这也是一个常数项的操作,那么这个递归算法的时间复杂度才是真正的$O(\log n)$**。
此时大家最后写出了这样的代码并且将时间复杂度分析的非常清晰,相信面试官是比较满意的。
@ -126,11 +126,11 @@ int function4(int x, int n) {
对于递归的时间复杂度,毕竟初学者有时候会迷糊,刷过很多题的老手依然迷糊。
**本篇我用一道非常简单的面试题目求x的n次方来逐步分析递归算法的时间复杂度注意不要一看到递归就想到了O(logn)**
**本篇我用一道非常简单的面试题目求x的n次方来逐步分析递归算法的时间复杂度注意不要一看到递归就想到了$O(\log n)$**
同样使用递归有的同学可以写出O(logn)的代码有的同学还可以写出O(n)的代码。
同样使用递归,有的同学可以写出$O(\log n)$的代码,有的同学还可以写出$O(n)$的代码。
对于function3 这样的递归实现很容易让人感觉这是O(logn)的时间复杂度其实这是O(n)的算法!
对于function3 这样的递归实现,很容易让人感觉这是$O(\log n)$的时间复杂度,其实这是$O(n)$的算法!
```CPP
int function3(int x, int n) {

View File

@ -29,7 +29,7 @@ i指向新长度的末尾j指向旧长度的末尾。
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
从前向后填充就是$O(n^2)$的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
@ -72,10 +72,10 @@ public:
return s;
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
此时算上本题,我们已经做了七道双指针相关的题目了分别是:

View File

@ -86,7 +86,7 @@ public:
# 题外话
一些同学热衷于使用substr来做这道题。
其实使用substr 和 反转 时间复杂度是一样的 都是O(n)但是使用substr申请了额外空间所以空间复杂度是O(n)而反转方法的空间复杂度是O(1)。
其实使用substr 和 反转 时间复杂度是一样的 ,都是$O(n)$但是使用substr申请了额外空间所以空间复杂度是$O(n)$,而反转方法的空间复杂度是$O(1)$
**如果想让这套题目有意义,就不要申请额外空间。**

View File

@ -72,8 +72,8 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
使用滚动数组,代码如下:
@ -95,8 +95,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
## 买卖股票的最佳时机II
@ -120,8 +120,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
【动态规划】
@ -161,8 +162,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 买卖股票的最佳时机III
@ -223,8 +224,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度:O(n * 5)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n × 5)$
当然大家可以看到力扣官方题解里的一种优化空间写法我这里给出对应的C++版本:
@ -248,8 +249,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
**这种写法看上去简单,其实思路很绕,不建议大家这么写,这么思考,很容易把自己绕进去!** 对于本题,把版本一的写法研究明白,足以!
@ -401,8 +402,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 买卖股票的最佳时机含手续费
@ -453,8 +454,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
## 总结

View File

@ -22,7 +22,7 @@ for (int i = 0; i < array.size(); i++) {
}
```
这个代码看上去好像是O(n)的时间复杂度其实是O(n^2)的时间复杂度因为erase操作也是O(n)的操作。
这个代码看上去好像是$O(n)$的时间复杂度,其实是$O(n^2)$的时间复杂度因为erase操作也是$O(n)$的操作。
所以此时使用双指针法才展现出效率的优势:**通过两个指针在一个for循环下完成两个for循环的工作。**
@ -30,7 +30,7 @@ for (int i = 0; i < array.size(); i++) {
在[字符串:这道题目,使用库函数一行代码搞定](https://programmercarl.com/0344.反转字符串.html)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。
使用双指针法,**定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。**时间复杂度是O(n)。
使用双指针法,**定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。**,时间复杂度是$O(n)$
在[替换空格](https://programmercarl.com/剑指Offer05.替换空格.html) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了!
@ -38,13 +38,13 @@ for (int i = 0; i < array.size(); i++) {
有同学问了,为什么要从后向前填充,从前向后填充不行么?
从前向后填充就是O(n^2)的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
从前向后填充就是$O(n^2)$的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。
**其实很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
那么在[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用双指针法用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。
那么在[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中,我们使用双指针法,用$O(n)$的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。
**在删除冗余空格的过程中如果不注意代码效率很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。**
**在删除冗余空格的过程中,如果不注意代码效率,很容易写成了$O(n^2)$的时间复杂度。其实使用双指针法$O(n)$就可以搞定。**
**主要还是大家用erase用的比较随意一定要注意for循环下用erase的情况一般可以用双指针写效率更高**
@ -74,22 +74,22 @@ for (int i = 0; i < array.size(); i++) {
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
时间复杂度可以做到$O(n^2)$,但还是比较费时的,因为不好做剪枝操作。
所以这道题目使用双指针法才是最为合适的,用双指针做这道题目才能就能真正体会到,**通过前后两个指针不算向中间逼近在一个for循环下完成两个for循环的工作。**
只用双指针法时间复杂度为O(n^2)但比哈希法的O(n^2)效率高得多哈希法在使用两层for循环的时候能做的剪枝操作很有限。
只用双指针法时间复杂度为$O(n^2)$,但比哈希法的$O(n^2)$效率高得多哈希法在使用两层for循环的时候能做的剪枝操作很有限。
在[双指针法:一样的道理,能解决四数之和](https://programmercarl.com/0018.四数之和.html)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环依然是使用双指针法。**
对于三数之和使用双指针法就是将原本暴力O(n^3)的解法降为O(n^2)的解法四数之和的双指针解法就是将原本暴力O(n^4)的解法降为O(n^3)的解法。
对于三数之和使用双指针法就是将原本暴力$O(n^3)$的解法,降为$O(n^2)$的解法,四数之和的双指针解法就是将原本暴力$O(n^4)$的解法,降为$O(n^3)$的解法。
同样的道理五数之和n数之和都是在这个基础上累加。
# 总结
本文中一共介绍了leetcode上九道使用双指针解决问题的经典题目除了链表一些题目一定要使用双指针其他题目都是使用双指针来提高效率一般是将O(n^2)的时间复杂度降为O(n)。
本文中一共介绍了leetcode上九道使用双指针解决问题的经典题目除了链表一些题目一定要使用双指针其他题目都是使用双指针来提高效率一般是将$O(n^2)$的时间复杂度,降为$O(n)$
建议大家可以把文中涉及到的题目在好好做一做,琢磨琢磨,基本对双指针法就不在话下了。

View File

@ -44,7 +44,7 @@ a->right = NULL;
在介绍前中后序遍历的时候有递归和迭代非递归还有一种牛逼的遍历方式morris遍历。
morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以将非递归遍历中的空间复杂度降为O(1),感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以将非递归遍历中的空间复杂度降为$O(1)$,感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
## 周二

View File

@ -72,16 +72,16 @@
**所以这块就说一说我个人理解,对内容持开放态度,集思广益,欢迎大家来讨论!**
子集问题分析:
* 时间复杂度:O(n * 2^n)因为每一个元素的状态无外乎取与不取所以时间复杂度为O(2^n),构造每一组子集都需要填进数组,又有需要O(n)最终时间复杂度O(n * 2^n)
* 空间复杂度O(n)递归深度为n所以系统栈所用空间为O(n)每一层递归所用的空间都是常数级别注意代码里的result和path都是全局变量就算是放在参数里传的也是引用并不会新申请内存空间最终空间复杂度为O(n)
* 时间复杂度:$O(n × 2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$,构造每一组子集都需要填进数组,又有需要$O(n)$,最终时间复杂度:$O(n × 2^n)$
* 空间复杂度:$O(n)$递归深度为n所以系统栈所用空间为$O(n)$每一层递归所用的空间都是常数级别注意代码里的result和path都是全局变量就算是放在参数里传的也是引用并不会新申请内存空间最终空间复杂度为$O(n)$
排列问题分析:
* 时间复杂度O(n!)这个可以从排列的树形图中很明显发现每一层节点为n第二层每一个分支都延伸了n-1个分支再往下又是n-2个分支所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。
* 空间复杂度O(n),和子集问题同理。
* 时间复杂度:$O(n!)$这个可以从排列的树形图中很明显发现每一层节点为n第二层每一个分支都延伸了n-1个分支再往下又是n-2个分支所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。
* 空间复杂度:$O(n)$,和子集问题同理。
组合问题分析:
* 时间复杂度:O(n * 2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
* 空间复杂度O(n),和子集问题同理。
* 时间复杂度:$O(n × 2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
* 空间复杂度:$O(n)$,和子集问题同理。
**一般说道回溯算法的复杂度,都说是指数级别的时间复杂度,这也算是一个概括吧!**

View File

@ -41,7 +41,7 @@
一些录友不清楚[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html)中时间复杂度是怎么来的?
就是快排O(nlogn)遍历O(n)加一起就是还是O(nlogn)。
就是快排$O(n\log n)$,遍历$O(n)$,加一起就是还是$O(n\log n)$
## 周三

View File

@ -79,7 +79,7 @@
[贪心算法K次取反后最大化的数组和](https://programmercarl.com/1005.K次取反后最大化的数组和.html)中的代码最后while处理K的时候其实直接判断奇偶数就可以了文中给出的方式太粗暴了哈哈Carl大意了。
例外一位录友留言给出一个很好的建议,因为文中是使用快排,仔细看题,**题目中限定了数据范围是正负一百,所以可以使用桶排序**这样时间复杂度就可以优化为O(n)了。但可能代码要复杂一些了。
例外一位录友留言给出一个很好的建议,因为文中是使用快排,仔细看题,**题目中限定了数据范围是正负一百,所以可以使用桶排序**,这样时间复杂度就可以优化为$O(n)$了。但可能代码要复杂一些了。
## 总结

View File

@ -53,11 +53,11 @@
文中涉及如下问题:
* 究竟什么是大O大O表示什么意思严格按照大O的定义来说快排应该是O(n^2)的算法!
* O(n^2)的算法为什么有时候比O(n)的算法更优?
* 究竟什么是大O大O表示什么意思严格按照大O的定义来说快排应该是$O(n^2)$的算法!
* $O(n^2)$的算法为什么有时候比$O(n)$的算法更优?
* 什么时间复杂度为什么可以忽略常数项?
* 如何简化复杂的时间复杂度表达式,原理是什么?
* O(logn)中的log究竟是以谁为底
* $O(\log n)$中的log究竟是以谁为底
这些问题大家可能懵懵懂懂的了解一些,但一细问又答不上来。
@ -70,9 +70,9 @@
# 周三
在[O(n)的算法居然超时了此时的n究竟是多大](https://programmercarl.com/前序/On的算法居然超时了此时的n究竟是多大.html)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时!
在[$O(n)$的算法居然超时了此时的n究竟是多大](https://programmercarl.com/前序/On的算法居然超时了此时的n究竟是多大.html)中介绍了大家在leetcode上提交代码经常遇到的一个问题-超时!
估计很多录友知道算法超时了,但没有注意过 O(n)的算法如果1s内出结果这个n究竟是多大
估计很多录友知道算法超时了,但没有注意过 $O(n)$的算法如果1s内出结果这个n究竟是多大
文中从计算机硬件出发,分析计算机的计算性能,然后亲自做实验,整理出数据如下:
@ -95,7 +95,7 @@
文中给出了四个版本的代码实现,并逐一分析了其时间复杂度。
此时大家就会发现同一道题目同样使用递归算法有的同学会写出了O(n)的代码有的同学就写出了O(logn)的代码。
此时大家就会发现,同一道题目,同样使用递归算法,有的同学会写出了$O(n)$的代码,有的同学就写出了$O(\log n)$的代码。
其本质是要对递归的时间复杂度有清晰的认识,才能运用递归来有效的解决问题!

View File

@ -8,7 +8,7 @@
在[贪心算法:加油站](https://programmercarl.com/0134.加油站.html)中给出每一个加油站的汽油和开到这个加油站的消耗,问汽车能不能开一圈。
这道题目咋眼一看感觉是一道模拟题模拟一下汽车从每一个节点出发看看能不能开一圈时间复杂度是O(n^2)。
这道题目咋眼一看,感觉是一道模拟题,模拟一下汽车从每一个节点出发看看能不能开一圈,时间复杂度是$O(n^2)$
即使用模拟这种情况,也挺考察代码技巧的。

View File

@ -211,8 +211,8 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(1)
* 时间复杂度:$O(n^2)$
* 空间复杂度:$O(1)$
贪心解法代码如下:
@ -232,8 +232,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
动规解法,版本一,代码如下:
@ -254,8 +255,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$
从递推公式可以看出dp[i]只是依赖于dp[i - 1]的状态。
@ -280,8 +282,8 @@ public:
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
* 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$
建议先写出版本一,然后在版本一的基础上优化成版本二,而不是直接就写出版本二。

View File

@ -49,7 +49,7 @@ a->right = NULL;
在介绍前中后序遍历的时候有递归和迭代非递归还有一种牛逼的遍历方式morris遍历。
morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以将非递归遍历中的空间复杂度降为O(1),感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
morris遍历是二叉树遍历算法的超强进阶算法morris遍历可以将非递归遍历中的空间复杂度降为$O(1)$,感兴趣大家就去查一查学习学习,比较小众,面试几乎不会考。我其实也没有研究过,就不做过多介绍了。
## [二叉树的递归遍历](https://programmercarl.com/二叉树的递归遍历.html)

View File

@ -22,7 +22,7 @@
例如要查询一个名字是否在这所学校里。
要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1) 就可以做到。
要枚举的话时间复杂度是$O(n)$,但如果使用哈希表的话, 只需要$O(1)$就可以做到。
我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。
@ -88,17 +88,17 @@
|集合 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
|---|---| --- |---| --- | --- | ---|
|std::set |红黑树 |有序 |否 |否 | O(logn)|O(logn) |
|std::multiset | 红黑树|有序 |是 | 否| O(logn) |O(logn) |
|std::unordered_set |哈希表 |无序 |否 |否 |O(1) | O(1)|
|std::set |红黑树 |有序 |否 |否 | $O(\log n)$|$O(\log n)$ |
|std::multiset | 红黑树|有序 |是 | 否| $O(\log n)$ |$O(\log n)$ |
|std::unordered_set |哈希表 |无序 |否 |否 |$O(1)$ | $O(1)$|
std::unordered_set底层实现为哈希表std::set 和std::multiset 的底层实现是红黑树红黑树是一种平衡二叉搜索树所以key值是有序的但key不可以修改改动key值会导致整棵树的错乱所以只能删除和增加。
|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
|---|---| --- |---| --- | --- | ---|
|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(logn)|O(logn) |
|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(logn) |O(logn) |
|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)|
|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | $O(\log n)$|$O(\log n)$ |
|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|$O(\log n)$ |$O(\log n)$ |
|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |$O(1)$ | $O(1)$|
std::unordered_map 底层实现为哈希表std::map 和std::multimap 的底层实现是红黑树。同理std::map 和std::multimap 的key也是有序的这个问题也经常作为面试题考察对语言容器底层的理解

View File

@ -302,11 +302,11 @@ if (startIndex >= nums.size()) { // 终止条件可以不加
**而使用used数组在时间复杂度上几乎没有额外负担**
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是$O(n)$但如果使用set去重空间复杂度就变成了$O(n^2)$因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
那有同学可能疑惑 用used数组也是占用$O(n)$的空间啊?
used数组可是全局变量每层与每层之间公用一个used数组所以空间复杂度是O(n + n)最终空间复杂度还是O(n)。
used数组可是全局变量每层与每层之间公用一个used数组所以空间复杂度是$O(n + n)$,最终空间复杂度还是$O(n)$
# 重新安排行程(图论额外拓展)
@ -380,24 +380,24 @@ used数组可是全局变量每层与每层之间公用一个used数组
以下在计算空间复杂度的时候我都把系统栈(不是数据结构里的栈)所占空间算进去。
子集问题分析:
* 时间复杂度O(2^n)因为每一个元素的状态无外乎取与不取所以时间复杂度为O(2^n)
* 空间复杂度O(n)递归深度为n所以系统栈所用空间为O(n)每一层递归所用的空间都是常数级别注意代码里的result和path都是全局变量就算是放在参数里传的也是引用并不会新申请内存空间最终空间复杂度为O(n)
* 时间复杂度:$O(2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$
* 空间复杂度:$O(n)$递归深度为n所以系统栈所用空间为$O(n)$每一层递归所用的空间都是常数级别注意代码里的result和path都是全局变量就算是放在参数里传的也是引用并不会新申请内存空间最终空间复杂度为$O(n)$
排列问题分析:
* 时间复杂度O(n!)这个可以从排列的树形图中很明显发现每一层节点为n第二层每一个分支都延伸了n-1个分支再往下又是n-2个分支所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。
* 空间复杂度O(n),和子集问题同理。
* 时间复杂度:$O(n!)$这个可以从排列的树形图中很明显发现每一层节点为n第二层每一个分支都延伸了n-1个分支再往下又是n-2个分支所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。
* 空间复杂度:$O(n)$,和子集问题同理。
组合问题分析:
* 时间复杂度O(2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
* 空间复杂度O(n),和子集问题同理。
* 时间复杂度:$O(2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。
* 空间复杂度:$O(n)$,和子集问题同理。
N皇后问题分析
* 时间复杂度O(n!) 其实如果看树形图的话直觉上是O(n^n)但皇后之间不能见面所以在搜索的过程中是有剪枝的最差也就是On!n!表示n * (n-1) * .... * 1。
* 空间复杂度O(n),和子集问题同理。
* 时间复杂度:$O(n!)$ ,其实如果看树形图的话,直觉上是$O(n^n)$但皇后之间不能见面所以在搜索的过程中是有剪枝的最差也就是On!n!表示n * (n-1) * .... * 1。
* 空间复杂度:$O(n)$,和子集问题同理。
解数独问题分析:
* 时间复杂度O(9^m) , m是'.'的数目。
* 空间复杂度O(n^2)递归的深度是n^2
* 时间复杂度:$O(9^m)$ , m是'.'的数目。
* 空间复杂度:$O(n^2)$递归的深度是n^2
**一般说道回溯算法的复杂度,都说是指数级别的时间复杂度,这也算是一个概括吧!**

View File

@ -226,11 +226,11 @@ public:
**而使用used数组在时间复杂度上几乎没有额外负担**
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过组合子集排列问题的空间复杂度都是O(n)但如果使用set去重空间复杂度就变成了O(n^2)因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
**使用set去重不仅时间复杂度高了空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是$O(n)$但如果使用set去重空间复杂度就变成了$O(n^2)$因为每一层递归都有一个set集合系统栈空间是n每一个空间都有set集合。
那有同学可能疑惑 用used数组也是占用O(n)的空间啊?
那有同学可能疑惑 用used数组也是占用$O(n)$的空间啊?
used数组可是全局变量每层与每层之间公用一个used数组所以空间复杂度是O(n + n)最终空间复杂度还是O(n)。
used数组可是全局变量每层与每层之间公用一个used数组所以空间复杂度是$O(n + n)$,最终空间复杂度还是$O(n)$
## 总结

View File

@ -57,15 +57,15 @@ for (int i = 0; i < a.size(); i++) {
在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。**
接着在[字符串:替换空格](https://programmercarl.com/剑指Offer05.替换空格.html)同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
接着在[字符串:替换空格](https://programmercarl.com/剑指Offer05.替换空格.html),同样还是使用双指针法在时间复杂度$O(n)$的情况下完成替换空格。
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
那么针对数组删除操作的问题,其实在[27. 移除元素](https://programmercarl.com/0027.移除元素.html)中就已经提到了使用双指针法进行移除操作。
同样的道理在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用O(n)的时间复杂度,完成了删除冗余空格。
同样的道理在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中我们使用$O(n)$的时间复杂度,完成了删除冗余空格。
一些同学会使用for循环里调用库函数erase来移除元素这其实是O(n^2)的操作因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。
一些同学会使用for循环里调用库函数erase来移除元素这其实是$O(n^2)$的操作因为erase就是$O(n)$的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。
# 反转系列

View File

@ -67,8 +67,8 @@
可以使用暴力解法,通过这道题目,如果追求更优的算法,建议试一试用二分法,来解决这道题目
暴力解法时间复杂度O(n)
二分法时间复杂度O(logn)
* 暴力解法时间复杂度:$O(n)$
* 二分法时间复杂度:$O(\log n)$
在这道题目中我们讲到了**循环不变量原则**,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。
@ -81,8 +81,8 @@
双指针法(快慢指针法):**通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。**
暴力解法时间复杂度O(n^2)
双指针时间复杂度O(n)
* 暴力解法时间复杂度:$O(n^2)$
* 双指针时间复杂度:$O(n)$
这道题目迷惑了不少同学,纠结于数组中的元素为什么不能删除,主要是因为以下两点:
@ -97,12 +97,12 @@
本题介绍了数组操作中的另一个重要思想:滑动窗口。
暴力解法时间复杂度O(n^2)
滑动窗口时间复杂度O(n)
* 暴力解法时间复杂度:$O(n^2)$
* 滑动窗口时间复杂度:$O(n)$
本题中,主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。
**滑动窗口的精妙之处在于根据当前子序列和大小的情况不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。**
**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将$O(n^2)$的暴力解法降为$O(n)$。**
如果没有接触过这一类的方法,很难想到类似的解题思路,滑动窗口方法还是很巧妙的。
@ -129,19 +129,5 @@
最后,大家周末愉快!
## 其他语言版本
Java
Python
Go
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>

View File

@ -141,7 +141,7 @@ cd a/b/c/../../
本题就要**使用优先级队列来对部分频率进行排序。** 注意这里是对部分数据进行排序而不需要对所有数据排序!
所以排序的过程的时间复杂度是O(logk)整个算法的时间复杂度是O(nlogk)。
所以排序的过程的时间复杂度是$O(\log k)$,整个算法的时间复杂度是$O(n\log k)$
# 总结

View File

@ -33,7 +33,7 @@ public:
耗时如下:
![vectorinsert](https://img-blog.csdnimg.cn/20201218203611181.png)
其直观上来看数组的insert操作是O(n)的整体代码的时间复杂度是O(n^2)。
其直观上来看数组的insert操作是$O(n)$的,整体代码的时间复杂度是$O(n^2)$
这么一分析好像和版本二链表实现的时间复杂度是一样的啊,为什么提交之后效率会差距这么大呢?
```CPP
@ -97,7 +97,7 @@ for (int i = 0; i < vec.size(); i++) {
**同时也注意此时capicity和size的变化关键的地方我都标红了**。
而在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中我们使用vector来做insert的操作此时大家可会发现**虽然表面上复杂度是O(n^2)但是其底层都不知道额外做了多少次全量拷贝了所以算上vector的底层拷贝整体时间复杂度可以认为是O(n^2 + t * n)级别的t是底层拷贝的次数**。
而在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中我们使用vector来做insert的操作此时大家可会发现**虽然表面上复杂度是$O(n^2)$但是其底层都不知道额外做了多少次全量拷贝了所以算上vector的底层拷贝整体时间复杂度可以认为是$O(n^2 + t × n)$级别的t是底层拷贝的次数**。
那么是不是可以直接确定好vector的大小不让它在动态扩容了例如在[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中已经给出了有people.size这么多的人可以定义好一个固定大小的vector这样我们就可以控制vector不让它底层动态扩容。
@ -133,7 +133,7 @@ public:
![vector手动模拟insert](https://img-blog.csdnimg.cn/20201218200626718.png)
这份代码就是不让vector动态扩容全程我们自己模拟insert的操作大家也可以直观的看出是一个O(n^2)的方法了。
这份代码就是不让vector动态扩容全程我们自己模拟insert的操作大家也可以直观的看出是一个$O(n^2)$的方法了。
但这份代码在leetcode上统计的耗时甚至比版本一的还高我们都不让它动态扩容了为什么耗时更高了呢
@ -151,7 +151,7 @@ public:
大家应该发现了编程语言中一个普通容器的insertdelete的使用都可能对写出来的算法的有很大影响
如果抛开语言谈算法,除非从来不用代码写算法纯分析,**否则的话语言功底不到位O(n)的算法可以写出O(n^2)的性能**,哈哈。
如果抛开语言谈算法,除非从来不用代码写算法纯分析,**否则的话,语言功底不到位$O(n)$的算法可以写出$O(n^2)$的性能**,哈哈。
相信在这里学习算法的录友们,都是想在软件行业长远发展的,都是要从事编程的工作,那么一定要深耕好一门编程语言,这个非常重要!

View File

@ -27,7 +27,7 @@
使用库函数最大的忌讳就是不知道这个库函数怎么实现的,也不知道其时间复杂度,上来就用,这样写出来的算法,时间复杂度自己都掌握不好的。
例如for循环里套一个字符串的inserterase之类的操作你说时间复杂度是多少呢很明显是O(n^2)的时间复杂度了。
例如for循环里套一个字符串的inserterase之类的操作你说时间复杂度是多少呢很明显是$O(n^2)$的时间复杂度了。
在刷题的时候本着我说的标准来使用库函数,详细对大家回有所帮助!

View File

@ -40,7 +40,7 @@ leetcode上没有纯01背包的问题都是01背包应用方面的题目
这样其实是没有从底向上去思考,而是习惯性想到了背包,那么暴力的解法应该是怎么样的呢?
每一件物品其实只有两个状态取或者不取所以可以使用回溯法搜索出所有的情况那么时间复杂度就是O(2^n)这里的n表示物品数量。
每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是$O(2^n)$这里的n表示物品数量。
**所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!**

View File

@ -89,7 +89,7 @@ int main() {
```
* 时间复杂度:O(m * n * k) m物品种类个数n背包容量k单类物品数量
* 时间复杂度:$O(m × n × k)$m物品种类个数n背包容量k单类物品数量
也有另一种实现方式就是把每种商品遍历的个数放在01背包里面在遍历一遍。
@ -125,7 +125,7 @@ int main() {
}
```
* 时间复杂度:O(m * n * k) m物品种类个数n背包容量k单类物品数量
* 时间复杂度:$O(m × n × k)$m物品种类个数n背包容量k单类物品数量
从代码里可以看出是01背包里面在加一个for循环遍历一个每种商品的数量。 和01背包还是如出一辙的。

View File

@ -70,7 +70,7 @@
这里我给出C/C++的定义链表节点方式,如下所示:
```
```cpp
// 单链表
struct ListNode {
int val; // 节点上存储的元素
@ -85,13 +85,13 @@ struct ListNode {
通过自己定义构造函数初始化节点:
```
```cpp
ListNode* head = new ListNode(5);
```
使用默认构造函数初始化节点:
```
```cpp
ListNode* head = new ListNode();
head->val = 5;
```
@ -120,9 +120,9 @@ head->val = 5;
![链表-添加节点](https://img-blog.csdnimg.cn/20200806195134331.png)
可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。
可以看出链表的增添和删除都是$O(1)$操作,也不会影响到其他节点。
但是要注意要是删除第五个节点需要从头节点查找到第四个节点通过next指针进行删除操作查找的时间复杂度是O(n)。
但是要注意要是删除第五个节点需要从头节点查找到第四个节点通过next指针进行删除操作查找的时间复杂度是$O(n)$
# 性能分析