leet-code 算法训练3

165 阅读2分钟

时间复杂度

图里说明了一切

数组在内存中的位置是连续的,属于顺序存储;链表是主动建立数据间的关联关系的,在内存中却不一定是连续的,属于链式存储

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(链表) 题法。跳过

  • 课后作业题

    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))
    }
    
    /**
    * 暴力循环
    */
    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))
    }
    
    // 后插入 双子针法
    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
    }
    
    

附上算法脑图

naotu.baidu.com/file/0a53d3…

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
}