时间、空间复杂度
时间复杂度
```js
function traverse(arr) {
var len = arr.length
for(var i=0;i<len;i++) {
console.log(arr[i])
}
}
T(n) = 1 + 1 + (n+1) + n + n + n + n*(n+1) + n*n + n*n = 3n^2 + 5n + 3
```
- 若 T(n) 是常数,那么无脑简化为1
- 若 T(n) 是多项式,我们只保留次数最高那一项,并且将其常数系数无脑改为1。
``` js
O(n)=n^2
```
> 常见的时间复杂度按照从小到大的顺序排列,有以下几种:
> 常数时间:O(1) 对数时间:O(logn) 线性时间:O(n) 线性对数时间:O(nlogn) 二次时间:O(n^2) 三次时间:O(n^3) 指数时间O(2^n)
空间复杂度
```js
function traverse(arr) {
var len = arr.length
for(var i=0;i<len;i++) {
console.log(arr[i])
}
}
```
- 循环体在执行时,并没有开辟新的内存空间,整个 traverse 函数对内存的占用量是恒定的,它对应的空间复杂度就是 O(1)。
```js
function init(n) {
var arr = []
for(var i=0;i<n;i++) {
arr[i] = i
}
return arr
}
```
- arr最终的大小是由输入的 n 的大小决定的,它会随着 n 的增大而增大、呈一个线性关系。因此这个算法的空间复杂度就是 O(n)。
DFS(深度优先搜索)和BFS(广度优先搜索)
const root = {
val: "A",
left: {
val: "B",
left: {
val: "D"
},
right: {
val: "E"
}
},
right: {
val: "C",
right: {
val: "F"
}
}
};
function DFS(root) {
if(!root) {
return
}
console.log('当前遍历的结点值是:', root.val)
preorder(root.left)
preorder(root.right)
}
function BFS(root) {
const queue = []
queue.push(root)
while(queue.length) {
const top = queue[0]
console.log(top.val)
if(top.left) {
queue.push(top.left)
}
if(top.right) {
queue.push(top.right)
}
queue.shift()
}
}
换个角度思考
空间换时间,Map类型(思想)存数据
> 描述: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
> 示例: 给定 nums = [2, 7, 11, 6, 3, 15], target = 9
> 返回 [[0, 1], [3, 4]]
```js
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
const twoSum = function (nums, target) {
const res = []
// 这里我用对象来模拟 map 的能力
const diffs = {}
// 缓存数组长度
const len = nums.length
// 遍历数组
for (let i = 0
// 判断当前值对应的 target 差值是否存在(是否已遍历过)
if (diffs[target - nums[i]] !== undefined) {
// 若有对应差值
res.push([diffs[target - nums[i]], i])
}
// 若没有对应差值,则记录当前值
diffs[nums[i]] = i
}
return res
}
```
双指针
> 描述给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
> 输入: 121
> 输出: true
```js
const validPalindrome = function(x) {
// 1. x < 0 的数字一定不是回文数,因为 -121 不等于 121-
// 2. 个位数是 0 的数字也一定不是回文数,0 除外
// if(x < 0 || (!(x % 10) && x)) return false
if (x < 0) return false
const s = x.toString()
// 缓存字符串的长度
const len = s.length
// i、j分别为左右指针
let b = 0, e = s.length - 1
// 当左右指针均满足对称时,一起向中间前进
while (b < e) {
if (s[b] !== s[e]) return false
++b, --e
}
return true
}
```
> 描述: 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
> 注意:答案中不可以包含重复的三元组。
> 示例:给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
```js
/**
* @param {number[]} nums
* @return {number[][]}
*/
const threeSum = function(nums) {
// 用于存放结果数组
let res = []
// 给 nums 排序
nums = nums.sort((a,b)=>{
return a-b
})
// 缓存数组长度
const len = nums.length
// 注意我们遍历到倒数第三个数就足够了,因为左右指针会遍历后面两个数
for(let i = 0
// 左指针 j
let j = i+1
// 右指针k
let k = len-1
// 如果遇到重复的数字,则跳过
if(i > 0 && nums[i] === nums[i - 1]) {
continue
}
while(j<k) {
// 三数之和小于0,左指针前进
if(nums[i] + nums[j] + nums[k] < 0) {
j++
// 处理左指针元素重复的情况
while(j < k && nums[j] === nums[j - 1]) {
j++
}
} else if(nums[i] + nums[j] + nums[k] > 0){
// 三数之和大于0,右指针后退
k--
// 处理右指针元素重复的情况
while(j<k && nums[k] === nums[k+1]) {
k--
}
} else {
// 得到目标数字组合,推入结果数组
res.push([nums[i],nums[j],nums[k]])
// 左右指针一起前进
j++
k--
// 若左指针元素重复,跳过
while(j<k&&nums[j]===nums[j-1]) {
j++
}
// 若右指针元素重复,跳过
while(j<k&&nums[k]===nums[k+1]) {
k--
}
}
}
}
// 返回结果数组
return res
}
```
动态规划
> 描述:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
> 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
1. 递归
```js
const climbStairs = function(n) {
if(n === 1) {
return 1
}
if(n === 2){
return 2
}
return climbStairs(n-1) + climbStairs(n-2)
};
```
2. 空间换时间
```js
const f = []
const climbStairs = function(n) {
if(n==1) {
return 1
}
if(n==2) {
return 2
}
if(f[n]===undefined) f[n] = climbStairs(n-1) + climbStairs(n-2)
return f[n]
};
```
3. 动态规划
```js
const climbStairs = function(n) {
const f = [];
f[1] = 1;
f[2] = 2;
for(let i = 3;i <= n;i++){
f[i] = f[i-2] + f[i-1];
}
console.log(f,"f")
return f[n];
};
```