Skip to content
On this page

下一个排列

leetcode 大意就是

  • 例如,arr = [1,2,3] 的下一个排列是 [1,3,2]
  • 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2]
  • arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

必须 原地 修改,只允许使用额外常数空间。

js
输入:nums = [1,2,3]
输出:[1,3,2]

输入:nums = [3,2,1]
输出:[1,2,3]

输入:nums = [1,1,5]
输出:[1,5,1]

思路

  • 如何变大:从低位挑一个大一点的数,交换前面一个小一点的数。
  • 变大的幅度要尽量小。

像 [3,2,1] 这样递减的,没有下一个排列,已经稳定了,没法变大。

像 [1,5,2,4,3,2] 这种,怎么稍微变大?

  • 从右往左,寻找第一个比右邻居小的数,把它换到后面去
    • “第一个”意味着尽量是低位,“比右邻居小”意味着它是从右往左的第一个波谷
  • 比如,1 5 (2) 4 3 2,中间这个 2。
  • 接着还是从右往左,寻找第一个比这个 2 大的数。15 (2) 4 (3) 2,交换后:15 (3) 4 (2) 2。
  • 还没结束!变大的幅度可以再小一点,仟位微变大了,后三位可以再小一点。
  • 后三位肯定是递减的,翻转,变成[1,5,3,2,2,4],即为所求。

代码

js
/**
 * @param {number[]} nums
 * @return {void} Do not return anything, modify nums in-place instead.
 */
function nextPermutation(nums) {
  let i = nums.length - 2; // 向左遍历,i从倒数第二开始是为了nums[i+1]要存在
  while (i >= 0 && nums[i] >= nums[i + 1]) i--; // 寻找第一个小于右邻居的数

  // 当前的 i 是存在的 从它身后挑一个数,和它换
  if (i >= 0) {
    let j = nums.length - 1;
    while (j >= 0 && nums[j] <= nums[i]) j--; // 寻找第一个大于 nums[i] 的数
    [nums[i], nums[j]] = [nums[j], nums[i]]; // 两数交换,实现变大
  }

  // 如果 i = -1,说明是递减排列,如 3 2 1,没有下一排列,直接翻转为最小排列:1 2 3
  let l = i + 1;
  let r = nums.length - 1;
  while (l < r) {
    // i 右边的数进行翻转,使得变大的幅度小一些
    [nums[l], nums[r]] = [nums[r], nums[l]];
    l++;
    r--;
  }
}

三数之和

力扣题目链接: 给你一个包含 n 个整数的数组  nums,判断  nums  中是否存在三个元素 a,b,c ,使得  a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

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

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

思路

  1. 做排序,如果 i > 0 后面也没有要遍历下去的必要
  2. 当前 i,定义指针 l = i + 1, r = len - 1, 当 l < r 情况下,计算 sum 值
    1. sum > 0, r--
    2. sum < 0, l++
    3. sum === 0
      1. 因为要求不含重复的三元组,这里再次对相同的元素值进行过滤
      2. l++ && r--

代码

js
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  nums.sort((a, b) => a - b); // 排序
  let len = nums.length;
  let result = [];
  for (let i = 0; i < len; i++) {
    // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
    if (nums[i] > 0) break;
    if (i > 0 && nums[i] == nums[i - 1]) continue; // 去重

    let [l, r] = [i + 1, len - 1];
    while (l < r) {
      let sum = nums[i] + nums[l] + nums[r];
      if (sum > 0) r--;
      else if (sum < 0) l++;
      else {
        // sum
        result.push([nums[i], nums[l], nums[r]]);
        while (l < r && nums[l] === nums[l + 1]) l++; // 去重
        while (l < r && nums[r] === nums[r - 1]) r--; // 去重
        l++;
        r--;
      }
    }
  }

  return result;
};

接雨水

leetcode

js
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)

思路

我们先明确几个变量的含义

js
leftMax: 左边的最大值,它是从左往右遍历找到的
rightMax: 右边的最大值,它是从右往左遍历找到的
left: 从左往右处理的当前下标
right: 从右往左处理的当前下标

在某个位置 i 处,它能存的水,取决于它左右两边的最大值中较小的一个,这点很好理解,和木桶原理类似,取决于短板。

如图,当 i = 4,他能存储的水量取决于左右两根柱子的最小高度

代码

js
/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function (height) {
  let sum = 0;
  let [left, right, leftMax, rightMax] = [0, height.length - 1, 0, 0];

  while (left < right) {
    leftMax = Math.max(leftMax, height[left]);
    rightMax = Math.max(rightMax, height[right]);

    // 以左边为准
    if (leftMax <= rightMax) {
      sum += leftMax - height[left];
      left++;
    } else {
      sum += rightMax - height[right];
      right--;
    }
  }

  return sum;
};

Released under the MIT License.