Q7-acwing789-数的范围

138 阅读3分钟

实现思路

1 难点是 转化题意:

  • 等于target的最左侧位置: >=target的 最小值
  • 等于target的最右侧位置: <=target的 最大值

转化后,就是一个典型的 二分查找问题

2 对 mid取值的 理解:

2.1 如果你要保留包含 mid 的那半边区间, 那么 mid 的取值方向,必须要能让新区间变小, 否则就可能陷入死循环

2.2 记忆方法:

  • 想保留左边[l, mid],mid 就要偏左
  • 想保留右边[mid, r],mid 就要偏右
  • "想保留哪边,mid 就要往哪个方向偏"

参考文档

01-原题链接

代码实现

方法1.1: 二分查找 时间复杂度: O(logn) 空间复杂度: O(1)

const readline = require("readline");
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

let inputArr = [];
let n, m;
let q = [];

rl.on("line", function (line) {
  inputArr.push(line.trim());

  // 第一行输入 n 和 m
  if (inputArr.length === 1) {
    [n, m] = inputArr[0].split(" ").map(Number);
  }
  // 第二行输入数组
  else if (inputArr.length === 2) {
    q = inputArr[1].split(" ").map(Number);
  }
  // 处理每个查询
  else if (inputArr.length === 2 + m) {
    // 处理所有查询
    for (let i = 2; i < inputArr.length; i++) {
      const x = Number(inputArr[i]);
      console.log(findRange(q, x));
    }
    rl.close();
  }
});

function findRange(nums, x) {
  const len = nums.length;
  // 查找左边界: >=x的最小值,就必然是 等于x的最左侧位置
  let l = 0, r = len - 1;
  // 获取 >=x 的最小值
  while (l < r) {
    let mid = (l + r) >> 1;
    const midVal = nums[mid];
    // 如果 midVal < x,说明 [l, mid]都<x,不是我们所需要的,所以左区间向右扩增
    if (midVal < x) l = mid + 1;
    // 如果 midVal >= x,说明 [mid, r]都>=x,而 我们要找的是最小的>=x的位置, 所以 右区间需要收缩
    else r = mid;
  }
  // 如果没找到目标值,说明nums中必然没有x,直接返回 -1 即可
  if (nums[l] !== x) return "-1 -1";
  // 记录左边界
  const resL = l;

  // 查找右边界: <=x的最大值,就必然是 等于x的最右侧位置
  l = 0, r = len - 1;
  while (l < r) {
    // 易错点: 这里需要 +1, 是因为在相邻数位置情况下,如果还是向下取整,l会一直赋值为mid,陷入死循环
    let mid = (l + r + 1) >> 1;
    const midVal = nums[mid];
    // 如果arr[mid] <= x,说明 [l, mid]都<=x,而 我们要找的是最大的<=x的位置, 所以 左区间需要扩增
    if (midVal <= x) l = mid;
    // 如果 arr[mid] > x,说明 [mid, r]都>x,不是我们所需要的, 所以 右区间需要收缩
    else r = mid - 1;
  }

  // 返回结果
  return `${resL} ${l}`;
}

方法1.2: 二分查找-递归 时间复杂度: O(logn) 空间复杂度: O(logn)

function findRange(nums, x) {
  // 起始位置:>=x的 最小值的idx; 结束位置:<=x的最大值的idx
  let frontIdx = findFront(nums, 0, nums.length - 1, x)
  let tailIdx = findTail(nums, 0, nums.length - 1, x)
  return `${frontIdx} ${tailIdx}`
}

// 起始位置:>=x的 最小值的idx
function findFront(arr, l, r, x) {
  if (l >= r) return arr[l] === x ? l : -1;
  const mid = l + (r - l >> 1)
  if (arr[mid] < x) {
    return findFront(arr, mid + 1, r, x)
  } else {
    return findFront(arr, l, mid, x)
  }
}

// 结束位置:<=x的最大值的idx
function findTail(arr, l, r, x) {
  if (l >= r) return arr[l] === x ? l : -1;
  const mid = l + (r - l + 1 >> 1)
  if (arr[mid] <= x) {
    return findTail(arr, mid, r, x)
  } else {
    return findTail(arr, l, mid - 1, x)
  }
}