Q57- code1095- 山脉数组中查找目标值 + Q58- code162- 寻找峰值

61 阅读3分钟

Q57- code1095- 山脉数组中查找目标值

实现思路

1 方法1:通用二分法

1.1 通过quickFind,快速找到分割点pdx

1.2 依次对(-1, pdx) 和 (pdx, len) 有序区间,进行二分查找

1.3 开区间表示 当前未处理的元素不包括 边界值

2 本题技巧:

  • 可以通过抽象出quickFind里的condition方法,来复用二分查找
  • 二分查找的重点是 找到分割点

参考文档

01- 方法1参考文档

代码实现

1 方法1- 通用二分法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
function findInMountainArray(target: number, arr: MountainArray): number {
  const len = arr.length();
  // 找峰值:第一个非递增的分割点
  const pdx = quickFind(-1, len, (i) => arr.get(i) > arr.get(i + 1));
  if (arr.get(pdx) === target) return pdx;

  // >=t的最小值(左侧升序部分)
  const ldx = quickFind(-1, pdx, (i) => arr.get(i) >= target);
  if (arr.get(ldx) === target) return ldx;

  // <=t的最大值(右侧降序部分)
  // 易错点1:如果这里是>=t,会导致错误的更新r边界,而不是更新l边界
  // 易错点2: 最后的返回值需要加上pdx的偏移量
  const rdx = pdx + quickFind(-1, len - pdx, (i) => arr.get(pdx + i) <= target);
  return arr.get(rdx) === target ? rdx : -1;
}

function quickFind(l: number, r: number, condition: (i: number) => boolean) {
  while (l + 1 < r) {
    const mid = l + ((r - l) >> 1);
    if (condition(mid)) {
      r = mid;
    } else {
      l = mid;
    }
  }
  return r;
}

Q58- code162- 寻找峰值

实现思路

1 方法1:二分查找

1.1 根据题目,总共有3种可能

  • 有序-降序:峰值就是第 0个元素,因为题目保证了nums[-1] = -∞
  • 有序-升序:峰值就是第 n-1个元素,因为题目保证了nums[n] = -∞
  • 无序- 即有峰值的情况:此时可以通过 idx 和 idx+1位置,进行比较
    • 如果 nums[idx] < nums[idx + 1],那么必然 [idx+1, n-1]会存在峰值 (由于我们不需要求 所有峰值中的最大峰值,所以可以放心排除掉 idx+1之前的所有值)

    • 同理,如果 nums[idx] > nums[idx + 1],那么必然 [0, idx]会存在峰值

2.1 二分本质:

  • 二分的本质 不是有序性/ 单调性
  • 而是 通过某种条件/性质,确定答案在哪一侧(从而每次都可 排除掉一半的搜索空间)

2.2 二分开区间写法 表示的含义

  • left 和 right表示的是,在[left+1,right-1]之间的 都是不能确定所属关系的元素;
  • left 和 right本身,是能确定所属关系的元素,所以不包括在内,是开区间

2.3 常见二分场景

  • 峰值/谷值:找局部最大/最小值
  • 旋转数组:找局部最大/最小值
  • 第K个问题:求最大/最小的 满足条件的值

参考文档

01- 方法1参考文档

02- 关于二分的本质介绍

代码实现

1 方法1- 通用二分法

  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
function findPeakElement(nums: number[]): number {
  // 易错点1:
  // 理论上:按开区间语义,r应该设置为len
  // 实际上:因为本题需要和 nums[mid+1]进行比较,必须保证mid+1不越界
  // 所以 本题妥协方案:设置 r = len - 1,虽然破坏了开区间的完美语义,但避免了越界
  const len = nums.length;
  let l = -1, r = len - 1;
  while (l + 1 < r) {
    const mid = (l + r) >> 1;
    if (nums[mid] < nums[mid + 1]) {
      l = mid;
    } else {
      // 此时 nums[mid] 必然> nums[mid + 1],峰值范围一定在[0, mid]之间
      // 因为 题目保证了 nums[i] != nums[i + 1]
      r = mid;
    }
  }
  return r;
}