算法-数组

170 阅读6分钟

合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n

  • nums1.length == m + n
  • nums2.length == n

合并再排序

var merge = function(nums1, m, nums2, n) {
    // 从m开始,把 nums2上元素添加到nums1中
    for(let i=0;i<n;i++) {
      nums1[m+i] = nums2[i]
    }
    // 排序 
    nums1.sort((a, b) => a - b)
};
  • nums1的m开始到末尾, 用nums2的元素代替,达到合并效果
  • nums1中的元素排序

双指针法

nums1nums2中元素都是有序的,可以把两个数组看做一个队列,每次从两个数组头部取出元素,进行比较,把较少的放到结果中

const merge = function (nums1, m, nums2, n) {
  // 数组看做队列,每次都从两个数组头部取出较少的放到结果中
  let p1 = 0 // nums1的头指针
  let p2 = 0 // nums2的头指针
  // 缓存结果
  let result = []

  // 当两个数组都遍历完才会跳出循环
  while (p1 < m || p2 < n) {
    // 如果nums1/nums2已经遍历完了,那么只能操作另外一个数组了~
    if (p1 === m) {
      result.push(nums2[p2++])
    } else if (p2 === n) {
      result.push(nums1[p1++])
    } else if (nums1[p1] < nums2[p2]) {
      result.push(nums1[p1++])
    } else {
      result.push(nums2[p2++])
    }
  }
  // 更新nums1
  for (let i = 0; i < m + n; i++) {
    nums1[i] = result[i]
  }
}

从后向前数组遍历

思路参考

从后向前数组遍历

准备3个指针:p1, p2, tail

  • p1指向数组nums1的有数字尾部
  • p2指向数组nums2的尾部
  • tail指向数组nums1的尾部
  • 每次遍历取出两数组中较大的值,填充到nums1[tail]
  • 最后,需要要判断nums2是否遍历完,如果没有遍历完,需要把剩余元素遍历到nums1中
const merge = function (nums1, m, nums2, n) {
  // p1指向数组nums1的有数尾部
  let p1 = m - 1
  // p2指向数组nums2的尾部
  let p2 = n - 1
  // tail指向数组nums1的尾部
  let tail = (m + n) - 1
  while (p1 >= 0 && p2 >= 0) {
    nums1[tail--] = nums1[p1] > nums2[p2] ?
      nums1[p1--]
      : nums2[p2--]
  }
  // 如果数组nums2还没有遍历完,继续遍历剩余元素到nums1中
  while (p2 >= 0) {
    nums1[tail--] = nums2[p2--]
  }
}

两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

思路

  • 提前准备一个map缓存遍历过的数和索引
  • 遍历数组,取出当前数
    • 计算另外一个数otherNum = target-nums[i]
    • 查询otherNum是否在map中
      • 如果存在,返回索引,结束循环
    • map中没有查询到,把当前数nums[i]添加到map中
const twoSum = function (nums, target) {
  // 遍历数组,计算target减去当前项的差,
  const map = new Map()
  for (let i = 0; i < nums.length; i++) {
    // 计算另外一个数,target - nums[i]
    const otherNum = target - nums[i]
    // 查询该数是否在map中
    if (map.has(otherNum)) {
      // 返回结果,提前结束函数
      return [map.get(otherNum), i]
    } else {
      // map没有找到,把当前数和索引存储到map中
      map.set(nums[i], i)
    }
  }
  return []
}

三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解法:排序 + 双指针

排序

  • 为了方便遍历过程中,去掉重复元素,需要对数组元素进行排序
  • 例如第一个数为nums[i],在获取第二个数和第三个数之前,可以先判断nums[i]===nums[i-1],如果当前数与前一个数相等,那么可以跳过本轮循环
  • 第二个数和第三个数也需要在做这种处理

思路

  • 判断数组长度是否小于3
    • 如果小于,直接返回空数组,提前结束函数
  • 对数组元素进行排序,按照从小到大
  • 准备一个空数组,存储达成条件的三元数组
  • 遍历数组
    • 取出当前数nums[i],如果nums[i]>0,数组已经是排序,无法达成三个数之和等于0,直接返回结果
    • i > 0 && nums[i]===nums[i-1],元素重复,跳过本次循环,避免出现重复结果
    • 设置左指针left=i+1,右指针right=nums[i].length-1
    • left < right成立,执行循环
      • 计算三数之和
      • 当和大于0, 说明nums[right]太大,right往前移
      • 当和小于0, 说明nums[left]太小,left往后移
      • 当和等于0,记录下此时的三个数,判断左指针和右指针是否和下一个元素相同,如果相同,继续把left和right移到下一个,直到没有重复元素,重新继续计算

图示

image.png

  • 实现代码:
/**
 * @param  {number[]}  nums
 * @return  {number[][]}
 */
const threeSum = function (nums) {
  // 数组长度小于3,直接结束
  if (nums.length < 3) {
    return []
  }
  // 按照从小到大给元素排序
  nums.sort((a, b) => a - b)

  // 存储三元数组的结果集
  const result = []
  const numsLen = nums.length

  // 遍历数组
  for (let i = 0; i < numsLen; i++) {
    const firstNum = nums[i]
    // 如果第一个数据大于0,后面结果肯定大于0,提前结束整个循环
    if (firstNum > 0) {
      break
    }
    // 当前数和前一个数相等,跳过本轮循环,进入下轮循环
    if (i > 0 && firstNum === nums[i - 1]) {
      continue
    }
    // 左指针,指向第二个数,向右边移动
    let left = i + 1
    // 右指针,指向第三个数,从数组末尾开始,往左边移动
    let right = numsLen - 1
    // 在while循环中,left一直向后移动,right指针一直往前移动,在这个过程中判断三个数之和是否等于0
    while (left < right) {
      // 计算三数之和
      const sum = firstNum + nums[left] + nums[right]
      if (sum > 0) {
        // nums[right]大了,向前移动
        right--
      } else if (sum < 0) {
        // nums[left]小了,向后移动
        left++
      } else if (sum === 0) {
        // 和为0,做记录
        result.push([firstNum, nums[left], nums[right]])
        // 相邻数去重
        while (left < right && nums[left] === nums[left + 1]) {
          left++
        }
        while (left < right && nums[right] === nums[right - 1]) {
          right--
        }
        left++
        right--
      }
    }
  }
  return result
}

数组去重

已知如下数组:

const  list = [3, 4, 5, 6, 5, 8, 3]

编写一个程序处理数组,最终获得元素不重复的数组

方式一:Set

// Set实现去重
const unique = (arr) => {
  return [...new Set(arr)]
}

方式二:Array.from

// Array.from实现去重
const unique2 = (arr) => {
  return Array.from(new Set(arr))
}

方式三:indexOf

// indexOf实现去重
const unique3 = (arr) => {
  const result = []
  arr.forEach(item => {
    if (result.indexOf(item) === -1) {
      result.push(item)
    }
  })
  return result
}

方式三:filter

// filter实现去重
const unique4 = (arr) => {
  return arr.filter((item, index) => {
    // 只保留第一次出现的
    return arr.indexOf(item) === index
  })
}

方式四:reduce

// reduce实现去重
const unique5 = (arr) => {
  return arr.reduce((pre, cur) => {
    if (pre.indexOf(cur) === -1) {
      pre.push(cur)
    }
    return pre
  }, [])
}

数组扁平化

请实现flat函数,实现数组扁平化

let arr1 = [
  1,
  [2, 3, 4],
  [5, [6, [7, [8]]]],
  10
]

flat函数特性说明

  • 数组.flat可以把嵌套数组拍平,变为一维数组

    • 该方法返回新数组,不影响原数组
    • 没有传递参数,默认拍平一层,可以传入数字,表明要拍平的层数
      • 传入参数<=0的参数,返回原数组,不拍平
      • 传入Infinity关键字,无论嵌套有多深,都为拍平为一维数组
      • 如果原数组有空位,Array.prototype.flat() 会跳过空位 包含空位的数组示例:
      const arr = [1, [2, 5], , ,]
      
  • 思路:

    • 如果数组长度小于等于0,返回原数组,提前结束函数
    • 如果数组长度>0,定义遍历result作为结果数组
      • 遍历数组
      • 判断当前元素是否是数组
        • 当前元素是数组,递归调用flat函数,当前元素作为新数组,depth减一作为新深度,并把flat函数的返回值展开,追加到结果数组中
      • 当前元素不是数组,添加到结果数组中
    • 最后返回结果数组

方式一:实现flat函数

const flat1 = (arr, depth = 1) => {
  // 如果depth小于等于0,返回原数组
  if (depth <= 0) {
    return arr
  }
  // 扁平化的数组
  const result = []
  arr.forEach(item => {
    if (Array.isArray(item)) {
      // 如果当前元素是数组,继续扁平化,depth减一
      // 将扁平化结果展开添加到result中
      result.push(...flat1(item, depth - 1))
    } else {
      result.push(item)
    }
  })
  return result
}
// 指定深度
console.log('flat1', flat1(list, 2))
// 使用 Infinity,可展开任意深度的嵌套数组
console.log('flat1', flat1(list, Infinity))
  • Array.prototype.push支持添加多个元素

方式二:reduce实现flat

// reduce实现flat函数
const flat2 = (arr, depth = 1) => {
  if (depth <= 0) {
    return arr
  }
  return arr.reduce((pre, cur) => {
    if (Array.isArray(cur)) {
      pre.push(...flat2(cur, depth - 1))
    } else {
      pre.push(cur)
    }
    return pre
  }, [])
}
// 指定深度
console.log('flat2', flat2(list, 2))
// 使用 Infinity,可展开任意深度的嵌套数组
console.log('flat2', flat2(list, Infinity))

计算两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

示例1:

输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例2:

输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
  • 说明

    • 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序
  • 思路

    • filter过滤,判断当前元素是否存在于另外一个数组中
    • 最后用Set对结果做去重,返回去重后的结果
  • 代码实现

// 使用filter过滤
// 最后用Set对结果数组做去重
const intersection = (arr1, arr2) => {
  const result = arr1.filter(item => {
    return arr2.indexOf(item) !== -1
  })
  return [...new Set(result)]
}

const nums1 = [4, 9, 5, 4]
const nums2 = [9, 4, 9, 8, 4]
// 使用
console.log('intersection', intersection(nums1, nums2))

计算多个数组的交集

要求:输出结果中的每个元素一定是唯一的

  • 思路
    • 使用 reduce 函数,两两比较,比较的结果与后一个数组比较
    • 使用Set对结果去重,返回去重后的结果
const intersection = (...args) => {
  if (args.length === 0) {
    return []
  }
  if (args.length === 1) {
    return args[1]
  }
  const result = args.reduce((arr1, arr2) => {
    // 每次比较两个数组的交集,比较的结果与后一个数组比较
    return arr1.filter(item => arr2.includes(item))
  })
  return [...new Set(result)]
}

const nums1 = [4, 9, 5, 4]
const nums2 = [9, 4, 5, 9, 8, 4]
const nums3 = [3, 4, 5, 6]
// 使用
console.log('intersection', intersection(nums1, nums2, nums3))