时间复杂度
图里说明了一切
数组在内存中的位置是连续的,属于顺序存储;链表是主动建立数据间的关联关系的,在内存中却不一定是连续的,属于链式存储
03 课
遇到算法,思路就是暴力求解(i=0,j=i+1)、快慢指针(i快,j慢)、双端收敛(i=0,j=len-1)。
/**
* 时间复杂度 O(n) 容易误解为O(n^2) ,因为代码的总操作(数组写入)为n
* 空间复杂度 O(1) 只使用常量空间
*/
function moveZero(arr) {
if (Array.isArray(arr)) {
let j = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) {
arr[j] = arr[i];
j++;
}
}
for (; j < arr.length; j++) {
arr[j] = 0;
}
}
return arr;
}
// 互换元素 双指针 快慢指针,互换
function moveZero2(nums) {
let i = 0;
let j = 0;
while (i < nums.length) {
if (nums[i] !== 0) {
if (nums[i] !== nums[j]) {
const temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
j++;
}
i++;
}
}
-
盛更多的水 (leetcode-cn.com/problems/co…
- n平方 时间复杂度 两层循环
function moreWater(arr){ if(!arr || arr.length === 1){ return 0 } let len = arr.length let maxArea = 0 for(let i=0;i<len;i++){ for(let j=i+1;j<len;j++){ let _area = (j-i) * Math.min(arr[i], arr[j]) maxArea = Math.max(maxArea, _area) } } return maxArea }
- O(n) 时间复杂度 双指针法 i=0 ,j=a.length。(夹逼准则\双端收敛)
function moreWater(arr){ let i=0,j=arr.length-1; let maxArea = 0; while(i<j){ let _area = (j-i) * Math.min(arr[i], arr[j]) arr[i] > arr[j] ? j--: i++ maxArea = Math.max(_area, maxArea) } return maxArea }
-
爬楼梯,爬到n级台阶有多少种方法。(leetcode.com/problems/cl…
- 递归 O(2^n次方) 时间复杂度 空间复杂度O(n) 缓存过的时间复杂度为O(n)
function climbStairs(n){ let _catch = {} // 对象 或者是[] function way(n){ if(n<=0) return 1 if(n<=2) return n if(_catch[n]) return _catch[n] _catch[n] = way(n-1) + way(n-2) return _catch[n] } return way(n) } // O(n) 保存最后的三个值 空间复杂度O(1) function climbStairs2(n){ let i=0,pre=1,cur=1,res=1; if(n<=0) return 1 if(n<=2) return n while(++i < n){ res = pre + cur pre = cur cur = res } return res } // o(n) 保存所有情况的值 // 滚动数组思想, 应用在了动态规划中. function climbStairs3(n){ const dp = []; dp[0] = 1; dp[1] = 1; for(let i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } // 通项公式 矩阵 时间复杂度会降低到O(logn) 空间复杂度 还是O(1)
扩展 字节跳动题目。n 级台阶,不能连续跳两次 (要么跳一个台阶,要么跳三个台阶)
假设: f(x) 表示爬到第 x 级台阶的方案数, g(x, 1) 表示爬到第 x 级台阶并且最后一步只跨越一个台阶的方案数, g(x, 2) 表示爬到第 x 级台阶并且最后一步跨越了两个台阶的方案数。
由 : f(x) = g(x, 1)+g(x,2), g(x, 1) = f(x-1), // 次数等与x-1 g(x, 2) = g(x-2, 1) // 最后一步跨越了两步,那么上一步只能跨越一步
得: f(x) = g(x, 1) + g(x, 2) = f(x-1) + g(x-2, 1) = f(x-1) + f((x-2)-1) = f(x-1) + f(x-3)
function climbStarts5(n) {
if(n <= 1) return 1; // 防止数组溢出(f[2])
let fArr = Array(n+1);
fArr[0] = 1;
fArr[1] = 1;
fArr[2] = 2;
for(int i = 3; i <= n; i++) {
fArr[i] = fArr[i-1] + fArr[i-3];
}
return fArr[n];
}
-
2数之和
- 两层循环 o(n^2)
function twoSum(num, target){ for(let i=0;i<num.length -1;i++){ for(let j=i+1;j<num.length;j++){ if(num[i]+num[j] === target){ return [i,j] } } } return [0,0] }
- hash 表 ? O(n)
function twoSum2(arr, target){ let keySet = {} let result = [] for(let i=0;i<arr.length;i++){ if(keySet[target - arr[i]] !== undefined){ result = result.concat([keySet[target - arr[i]], i]) }else{ keySet[arr[i]] = i } } return result }
-
3数之和 a+b+c=0 即 a+b=-c (leetcode-cn.com/problems/3s…
- 暴力求解 O(n^3)
- 排序(nlog(n)) + 双指针双端收敛 O(n2)
function threeSum (nums){ let ans = [] const len = nums.length; if(nums === null || len <3) return ans; nums.sort((a, b) => a-b); // 排序 for(let i=0;i<len;i++){ if(nums[i]>0) break; // 三数一定大于0 因为排序过的,当前大于0最后一定大于0 if(i>0 && nums[i] === nums[i-1]) continue // 下一个 去重 let L = i+1; let R = len -1 while(L<R){ const sum = nums[i] + nums[L] + nums[R]; if(sum === 0){ ans.push([nums[i], nums[L],nums[R]]); // 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++ while (L<R && nums[L] == nums[L+1]) { L++; // 下一个去重 } // 当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R-- while (L<R && nums[R] == nums[R-1]) { R--; // 下一个去重 } L++; R--; } else if(sum <0) { L++; } else if(sum > 0){ R-- } } } return ans; }
-
实战练习题目 link-list(链表) 题法。跳过
-
课后作业题
- 删除已排序数组中的重复项, 返回移除后数组的新长度(leetcode-cn.com/classic/pro…)
function removeDuplicates(nums){ let len = nums.length // 需提出 for(let i=1;i<len;i++ ){ if(nums[i] !== undefined && nums[i] === nums[i-1]){ nums.splice(i-1,1) i-- } } return nums.length } // 快慢指针法 function removeDuplicates2(nums){ if(nums === null || nums.length === 0) return 0; let i=0,j=1; for(j;j<nums.length;j++){ if(nums[j] !== nums[i]){ if(j-i > 1){ // 防止都不同的数值空复制 nums[i+1] = nums[j] } i++; } } return i+1; }
// 数组去重 Array.from(new Set(arr)) [...new Set(arr)] function unique(arr) { //定义常量 res,值为一个Map对象实例 const res = new Map(); //过滤条件是,如果res中没有某个键,就设置这个键的值为1 return arr.filter((a) => !res.has(a) && res.set(a, 1)) }
- 旋转数组 (leetcode-cn.com/problems/ro…)
/** * 暴力循环 */ function rotate(nums, k){ for(let i=0;i<k;i++){ nums.unshift(nums.pop()) } } /** * splice 插入元素 */ function rotate1(nums, k){ // 1 nums.splice(0, 0, ...nums.splice(nums.length -k)) // 2 nums.unshift(...nums.splice(nums.length - k)) }
- 合并两个有序数组 (leetcode-cn.com/problems/me…)
// 后插入 双子针法 function merge(nums1, m, nums2, n){ let len1 = m - 1; let len2 = n -1; let len = m + n - 1; while(len1 >=0 && len2 >=0){ // 把最大的放最后 nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--]: nums2[len2--]; } // 补上nums2 都是最小的 for(let i=0;i<len2+1;i++){ nums1[i]=nums2[i]; } } // 2、合并后排序 function merge2(num1, num2){ return num1.concat(num2).sort((a,b)=> a-b) }
// 末尾无进位 有进位中间停止, 有进位不停止 function plusOne(digits){ const len = digits.length; for(let i=len - 1;i>=0;i--){ digits[i]++ digits[i] %= 10; if(digits[i] !=0){ // 结束,说明进位没超 return digits } } // 进位超出 digits.unshift(1); return digits }
附上算法脑图
04课、栈、队列、双端队列
栈、队列、双端队列 - 添加、删除 都是O(1) 查询是O(n) priority Queue 优先队列 - 插入O(1) 取出 O(log(n))
链表 & 遍历一个链表
链表的数据结构: val属性存储当前的值,next属性存储下一个节点的引用
function ListNode (x){
this.val = x
this.next = null
}
function printList(head){
const array = []
while(head){
array.unshift(head.val)
head = head.next
}
return array
}