[路飞]_夜里无人演算法

804 阅读1分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

今日目标

哈希_两数之和
两数平方和
二路归并排序
二分查找
最长递增子序列

哈希_两数之和

两数之和是否等于目标值;上来我就for循环;
暴力得到任意两数之和与目标值匹配,返回两者的下标;
当然这中O(n^2)的时间复杂度肯定是要优化一下的;

var twoSum = function(nums, target) {
    for(let i = 0 ; i < nums.length ; i++){
        for(let j = i +1 ; j < nums.length ; j++){
            const t = nums[i] + nums[j];
            if(t === target) return [i,j];
        }
    }  
};

上述方法基本没什么难度; 通过上述代码可以了解这个思路时间复杂度为什么那么高?对于任意两个数字a、b;
如果 a + b = target;则有 target - a = b; 通过上述分析,如果将查询过的数据a和需要的数据b放在哈希表中;是不会可以降低时间复杂度?

比如数组 nums = [3,7,2,1], target = 9;

  1. 声明个哈希表map
  2. 拿到3,哈希表中没有3,target-3 = 6;将6放入map,map中key是6、value是0;
  3. 拿到7,哈希表中没有7,target-7 = 2;将2放入map,map中key是2、value是1;
  4. 拿到2,和希表中有2,返回map中2的value和当前下标[2,1]

比如数组nums = [4,5,2,6], target = 10 ;

  1. 声明个哈希表map
  2. 拿到4,哈希表中没有4,target-4 = 6;将6放入map,map中key是6、value是0;
  3. 拿到5,和希表中有5,target-5 = 5;将5放入map,map中key是5、value是1;
  4. 拿到2,和希表中有2,target-2 = 8;将8放入map,map中key是8、value是2;
  5. 拿到6,和希表中有6,返回map中7的value和当前下标[0,4]

根据上述思路编辑代码如下:

var twoSum = function(nums, target) {
    const map = {};
    for(let i = 0 ; i < nums.length ; i++){
        const n = nums[i];
        const k = target - n;
          if(map[n] === undefined){
            map[k] = i;
        }else{
            return [i,map[n]]

        }
    }
    return []
};

两数平方和

咋一看,题目描述简单,要求也简单;那就简简单单来个思路:

  1. 两个整数a、b的平方是否能组成c;
  2. 确定了a,b的范围只能是0、1、4、9、16......;
  3. 通过2确定了a,b是单调递增的
  4. 单调性,肯定考虑万能的for循环啊;
  5. 在根据两数之和的思路知道a,知道目标c;求b?

直接套代码吧!!

var judgeSquareSum = function (c) {
  const n = Math.sqrt(c)
  // 直接能开方,返回true
  if (n % 1 === 0) return true
  const end = Math.floor(n);
  
  // 声明个空哈希表
  const map = {}
  for (let i = 0; i <= end; i++) {
    const t = Math.pow(i, 2);
    
    // 拿到【b】
    const r = c - t
    map[t] = 1
    if (map[r] === undefined) {
      map[r] = 1
    } else {
    // 哈希表中找到到值,返回true
      return true
    }
  }
  return false
}

AC搞定;

审查代码,不对呀;两数之和中的【b】没什么规律,所以需要哈希表存储;在这个题中,b是有规律的,b的开方是整数即可满足条件,所以优化一下代码:

var judgeSquareSum = function (c) {
  const n = Math.sqrt(c)
  // 直接能开方,返回true
  if (n % 1 === 0) return true
  const end = Math.floor(n);
  for (let i = 0; i <= end; i++) {
    const t = Math.pow(i, 2);
    // 拿到【b】
    const r = Math.sqrt(c - t);
    // 开方为整数,返回true
    if(Math.floor(r) === Math.ceil(r)) return true
  }
  return false
}

单调性,单调递增性,哪不得来个双指针法呀;双指针,但不二分😄;
左指针指地(最小值);
右指针指天(最大值); 左右指针向中间前进;哈哈
代码如下

var judgeSquareSum = function (c) {
    
  let left = 0;
  let right = Math.ceil(Math.sqrt(c));
  while(left <= right){
      const t = Math.pow(left,2) + Math.pow(right,2);
      if(t === c) return true;
      if(t < c){
          left = left+1;
      }else{
          right = right-1;
      }
  }
  return false
}

二路归并排序

排序算法的有一种思路;

排序算法核心通过尽量少的比较将数据放在合适的位置

时间复杂度:O(nlogn)
空间复杂度:O(n) 递归是比较消耗空间


 //归并排序
var sortArray = function(nums) {

    //获取数组长度
    const len = nums.length;

    //如果数组长度小于2返回数组
    if(len < 2) return nums;

    // 否则分割数组
    const mid = len >> 1;//右移1位,类似除以2

    // 递归分解左侧数组
    const left = sortArray(nums.slice(0,mid));

    // 递归分解右侧数组
    const right = sortArray(nums.slice(mid,len))

    // 合并左右两个数组
   return merge(left,right);


    // 合并数组
    function merge(left,right){
        let l = 0;
        let r = 0;
        let list = [];
        while(l < left.length && r < right.length){
            if(left[l] < right[r]){
                list.push(left[l])
                l++
            }else{
                list.push(right[r])
                r++
            }

        }
        // 这两行代码省去了两个判断
        list = list.concat(left.slice(l,left.length));
        list = list.concat(right.slice(r,right.length));
        return list
    }

};

二分法

二分法条件:

有序、有目标; 二分法大致伪代码

let left = 0;
let right = max;
const m =  (left + right) >> 1;
if(m < target){
    left = m+1
}else{
    right = m-1
}

实际代码

var search = function (nums, target) {
  let left = 0
  let right = nums.length - 1
  while (left <= right) {
   // console.log(left, right)
    const mid = Math.floor(left + (right - left) / 2)
    if (nums[mid] === target) return mid
    if (nums[mid] < target) {
      left = mid + 1
    } else {
      right = mid - 1
    }
  }
  return -1
}

最长递增子序列

思路在注释中


/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  const len = nums.length
  // 数组长度为1,直接返回
  if (len === 1) return 1;
  let idx = 1;
  //声明一个单调递增的数组;该数组中最后一位数字是当前数组中最大值
  const list = []
  list[idx] = nums[0]
  for (let i = 1; i < len; i++) {
      // 如果数组最大值小于新增数值,将数组长度加+1
    if (list[idx] < nums[i]) {
      idx++
      list[idx] = nums[i]
    } else {
    // 如果当前数值小于最小值;
    // 用二分法找到一个位置,
    // 该位置的条件是上一位比当前数小,下一位比当前数大
    // 将当前(nums[i])数替换该位置的数;
      let left = 1
      let right = idx
      const n = nums[i]
      let pos = 0
      while (left <= right) {
        const m = (left + right) >> 1
        if (list[m] < n) {
          pos = m
          left = m + 1
        } else {
          right = m - 1
        }
      }
      // 替换该位置
      list[pos + 1] = nums[i]
    }
  }
  return idx
}