序列的第k小数问题 | 豆包MarsCode AI刷题

185 阅读4分钟

在这篇文章中,我们需要解决一道关于序列的第k小数问题,给定一个长度为n的整数序列a,找到区间[l,r](区间长度大于等于k),计算这个区间第k小的数等于x(l,r)有多少对。

image.png

首先,我们可以采用直接遍历的方法,利用双层for循环遍历每个长度大于等于k的数组,再对其排序,判断第k小的数是不是x,如果是,则计算数组中小于x的个数。具体实现如下;

function solution(n, x, k, a) {
  let count = 0;

  // 辅助函数,用于计算数组中小于等于value的元素个数
  function countNum(nums, value) {
    let count = 0;
    for (let num of nums) {
      if (num <= value) count++;
    }
    return count;
  }

  // 遍历数组,寻找所有可能的(l, r)对
  for (let l = 0; l <= n - k; l++) {
    for (let r = l + k - 1; r < n; r++) {
      // 切片数组从l到r(包括r)
      let subArray = a.slice(l, r + 1);
      // 对子数组进行排序
      let sortedArr = [...subArray].sort((a, b) => a - b);
      // 找到子数组中第k小的数的位置
      let index = k - 1;
      // 检查第k小的数是否等于x
      if (sortedArr[index] === x) {
        // 检查子数组中小于等于x的元素个数是否至少为k
        if (countNum(sortedArr, x) >= k) {
          count++;
        }
      }
    }
  }

  return count;
}

定义一个solution方法接收参数n数组长度,x第k小的数,k子数组长度最小值,a数组,然后定义一个count用于记录符合要求的个数,开始遍历数组,通过两层嵌套for循环,外层循环变量 l 表示区间的左端点,内层循环变量 r 表示区间的右端点,在每次内层循环中,代码会提取区间 [l, r] 的子数组 subArray,然后对子数组进行排序,得到 sortedArr,计算子数组中第k小的数的位置,即 index = k - 1,再判断如果第k小的数等于 x,则使用 countNum 函数检查子数组中小于等于 x 的元素个数是否至少为 k。如果满足,则count++,最后return

除此,还可用双指针的方法解决。先定义一个window数组窗口,利用双指针左边界l和右边界r,将当前元素加入窗口,判断确保当前窗口长度不小于k的情况下,对当前窗口进行排序,判断第k小的数是否为x,若是,则加1。具体实现如下;

function solution(n, x, k, a) {
  let count = 0;

  // 使用双指针 l 和 r
  for (let l = 0; l < n; l++) {
    const window = [];

    for (let r = l; r < n; r++) {
      // 将当前元素加入窗口
      window.push(a[r]);

      // 确保窗口长度不小于 k
      if (r - l + 1 >= k) {
        // 对当前窗口进行排序
        const sortedWindow = [...window].sort((a, b) => a - b);

        // 获取第 k 小的数
        if (sortedWindow[k - 1] === x) {
          count++;
        }
      }
    }
  }

  return count;
}
const sortedWindow = [...window].sort((a, b) => a - b);

这样做的目的是为了在不改变原始 window 数组的情况下,对其进行排序,如果直接window 数组调用 sort() 方法,会改变原数组的顺序。例如查找第 k 小的元素。这样做可以保证我们在处理窗口的数据时不会影响原来的窗口状态。

个人总结

双指针基本概念:

  • 两个指针:通常使用两个指针(例如 left 和 right),这两个指针可以从不同的端点开始移动,或者一个固定,另一个在变化。
  • 滑动窗口:在处理子数组或子序列时,一个常用的方法是将一个指针固定在起始位置,而另一个指针滑动到结束位置,这样可以有效地维护当前子结构的状态。
  • 条件更新:根据某些条件(如累积和、元素数量等),动态调整指针的位置,以便找到符合要求的结果。

主要使用场景:

  1. 查找配对:在有序数组中查找两个数之和为目标值的配对。
  2. 最小/最大子数组/子序列:寻找满足条件的最小或最大长度的子数组。
  3. 去重与分组:处理重复元素,或将数组分成两部分等。
  4. 字符串处理:判断是否是回文串、去除重复字符等。

双指针是一种常见的算法技巧,通常用于解决数组或链表相关的问题。它可以减少不必要的遍历次数,逻辑清晰明了,便于掌握和应用。