⚡今日算法 -- 选择工作问题 | 计算排序的最少子数组元素个数

117 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

今天我们来做两道算法题吧,分别是选择工作问题计算排序的最少子数组元素个数

选择工作问题

题目描述

给定数组 hard 和 money,长度都为 N

hard[] 表示 i 号的难度,money[] 表示 i 号工作的收入

给定数组 ability,长度都为 M, ability[] 表示 j 号人的能力

每一号工作,都可以提供无数的岗位,难度和收入都一样

但是人的能力必须 >= 这份工作的难度,才能上班

返回一个长度为 M 的数组 ans, ans[j] 表示 j 号人能获得的最好收入

思路

  1. 抽象出一个Job对象,用来描述每份工作的难度和对应的收入

  2. 首先对所有工作进行排序,按照难度升序,难度相同的情况下按照收入降序排序 这样做的目的是当我在面临难度相同的工作时,我直接选择第一份工作即可保证获得该难度下的最高收入

  3. 维护一个map,以难度为key,收入为value,每个难度的工作都只保存其最高收入,而遇到更高难度的工作时,如果其收入并没有低难度的工作收入高的话则不将其加入map

  4. 生成这样一个map之后,遍历该map,找到难度小于等于员工能力的那份工作,即可得到其最高收入

代码

/**
 * @param hard 每份工作的难度数组
 * @param money 每份工作的收入数组
 * @param ability 员工的能力数组
 * @returns 每个员工能够获得的最高收入数组
 */
export const solution = (
  hard: number[],
  money: number[],
  ability: number[],
): number[] => {
  const n = hard.length

  // 1. 抽象出 Job 对象,描述每份工作的难度和收入
  interface Job {
    hard: number
    money: number
  }

  // 生成 jobs 数组
  const jobs = hard.map(
    (item, idx) =>
      ({
        hard: item,
        money: money[idx],
      } as Job),
  )

  // 2. 对 jobs 数组排序 确保获得最优收入
  jobs.sort((a, b) =>
    a.hard - b.hard === 0 ? b.money - a.money : a.hard - b.hard,
  )

  // 3. 维护一个以 Record<hard, money> 的 map,确保是按照 hard 和 money 递增的
  const map = new Map<Job['hard'], Job['money']>()

  // 第一份工作它的难度最低 薪水又是在该难度中最高的 将其加入到 map 中
  map.set(jobs[0].hard, jobs[0].money)

  // 用于记录上一次遍历的工作
  let preJob: Job = jobs[0]
  for (const job of jobs) {
    // 只有当遍历的工作它的难度和上一次遍历的工作不同 且 收入高于上一次遍历的工作时才将其加入 map
    if (job.hard !== preJob.hard && job.money > preJob.money) {
      preJob = job
      map.set(job.hard, job.money)
    }
  }

  // 4. 使用二分找到与每个员工能力最接近的那个工作难度 其收入绝对是最优的
  let ans = new Array<number>(ability.length)
  const findNearestHard = (ability: number) => {
    const arr = Array.from(map.keys())
    let left = 0
    let right = arr.length - 1

    while (left <= right) {
      const mid = left + ((right - left) >> 2)

      if (arr[mid] < ability) {
        left = mid + 1
      } else if (arr[mid] > ability) {
        right = mid - 1
      } else {
        return arr[mid]
      }
    }

    // 当 ability 不存在于 arr 中时 返回 arr[right] 即 ability 的左侧边界
    return arr[right]
  }

  ability.forEach((item, idx) => {
    const nearestHard = findNearestHard(item)
    ans[idx] = map.get(nearestHard)!
  })

  return ans
}

// map => [[1, 120], [2, 300], [3, 800], [5, 900]]
// ability => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// ans => [120, 300, 800, 800, 900, 900, 900, 900, 900, 900]
const ans = solution(
  [1, 2, 3, 1, 4, 2, 5],
  [100, 300, 800, 120, 400, 200, 900],
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
)
console.log(ans)

单元测试

describe('01-选择工作问题', () => {
  test('happy path', () => {
    const hard = [1, 2, 3, 1, 4, 2, 5]
    const money = [100, 300, 800, 120, 400, 200, 900]
    const ability = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    const ans = solution(hard, money, ability)

    expect(ans).toEqual([120, 300, 800, 800, 900, 900, 900, 900, 900, 900])
  })
})

计算排序的最少子数组元素个数

给你一个数组,只允许你排序这个数组的子数组一次让整体有序,求出这个子数组的元素个数的最小值

例:

  1. arr = [1, 2, 6, 5, 4, 3, 8, 9] ==> res === 4 解释: 只用排序[6, 5, 4, 3]这个子数组即可让整体有序,这个子数组的长度为4

思路

  1. 首先从左往右遍历,找出最右边递增数组的左边界元素的下标
  2. 再从右往左遍历,找出最左边递减数组的右边界元素的下标
  3. 然后左右边界之间的元素个数即为答案

比如 arr = [1, 2, 6, 5, 4, 3, 8, 9] 中

从左往右遍历找出最右边递增数组[3, 8, 9]的左边界元素3的下标为5

从右往左遍历找出最左边递减数组[1, 2, 6]的右边界元素6的下标为2

最后两个下标之间的元素个数为5 - 2 + 1 === 4即为答案

代码

export const solution = (arr: number[]) => {
  // 从左往右遍历找出右边递增数组的左边界
  let max = arr.at(0)!
  let noMaxIdx = -1
  for (let i = 1; i < arr.length; i++) {
    const item = arr.at(i)!

    if (item < max) {
      noMaxIdx = i
    } else {
      max = Math.max(max, item)
    }
  }

  // 从右往左遍历找出左边递减数组的右边界
  let min = arr.at(-1)!
  let noMinIdx = arr.length - 1
  for (let i = arr.length - 2; i >= 0; i--) {
    const item = arr.at(i)!

    if (item > min) {
      noMinIdx = i
    } else {
      min = Math.min(min, item)
    }
  }

  // noMaxIdx - noMinIdx + 1 即为答案
  return noMaxIdx - noMinIdx + 1
}

单元测试

describe('02-计算排序的最少子数组元素个数', () => {
  test('happy path', () => {
    const arr = [1, 2, 6, 5, 4, 3, 8, 9]

    const res = solution(arr)

    expect(res).toBe(4)
  })
})