LeetCode 之算法篇(1)

本篇文章做leetcode top 100的的一些内容总结,以供后面复习使用

题目

41. 缺失的第一个正数 (未掌握)

这道题的关键点在于对桶排序变形,或者说借助桶排序的思想。在桶排序中,我们会将元素放入对应桶中,然后按顺序取出桶中元素。

1、将数组中的每个元素放到正确的位置: 对于每个元素 nums[i],如果它在区间 [1, n] 内,将它放到正确的位置上,即 nums[i] 应该在数组中的索引位置 nums[i]-1 上。这样一来,遍历完数组后,第一个不在正确位置上的元素就是缺失的最小正整数。
2、查找第一个不在正确位置上的元素: 继续遍历数组,找到第一个满足 nums[i] != i + 1 的元素,即 nums[i] 就是缺失的最小正整数。

/**
 * @param {number[]} nums
 * @return {number}
 */
var firstMissingPositive = function(nums) {
    const n = nums.length;

    for (let i = 0; i < n; i++) {
        while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] !== nums[i]) {
            // 将 nums[i] 放到正确的位置
            [nums[nums[i] - 1], nums[i]] = [nums[i], nums[nums[i] - 1]];
        }
    }

    // 查找第一个不在正确位置上的元素
    for (let i = 0; i < n; i++) {
        if (nums[i] !== i + 1) {
            return i + 1;
        }
    }

    // 如果数组中都是正确位置的元素,则缺失的是 n+1
    return n + 1;
};

如果看完上面这段代码,还是不理解。为什么是这么一段代码,主要有两处代码
1、nums[nums[i] - 1] !== nums[i]
2、[nums[nums[i] - 1], nums[i]] = [nums[i], nums[nums[i] - 1]]

while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] !== nums[i]) {
    // 将 nums[i] 放到正确的位置
    [nums[nums[i] - 1], nums[i]] = [nums[i], nums[nums[i] - 1]];
}

首先这段代码作用就是将nums[i]放到正确的位置。

Q:什么是正确的位置?
A:一般来说,数组的索引是从 0 开始的。所以,如果元素 nums[i] 是最小正整数 1,那么它应该放置在数组的索引位置 0 上;如果 nums[i] 是最小正整数 2,那么它应该放置在数组的索引位置 1 上,以此类推。那么 nums[i] 的正确位置就是 nums[i] - 1(比如nums[2]的位置在1上面,nums[2] - 1 = 1)

条件 nums[nums[i] - 1] !== nums[i] 的意思是:当前元素 nums[i] 不在正确的位置上。

具体来说,nums[i] - 1 表示 nums[i] 在数组中正确的位置,因为数组索引是从 0 开始的,而我们希望最小正整数从 1 开始。所以,如果 nums[i] 已经在正确的位置上,那么 nums[i] 应该等于 nums[nums[i] - 1]。

如果 nums[i] 不在正确的位置上,即 nums[i] - 1 处的值不等于 nums[i],就需要进行位置交换。这样一来,通过循环的一系列位置交换,每个元素最终都会被放到它正确的位置上。

这个条件的目的是避免不必要的交换操作,提高算法效率。因为如果元素已经在正确的位置上,就不需要进行交换了,直接继续遍历下一个元素。

OK,那么位置交换代码就是 [nums[nums[i] - 1], nums[i]] = [nums[i], nums[nums[i] - 1]];

将当前元素 nums[i] 放到它在数组中正确的位置上,即 nums[i] - 1 处。这样的操作保证了数组的结构类似于桶排序,每个元素都在正确的位置上。

这种位置交换的思路是为了满足题目中对于最小正整数的要求,同时在 O(n) 时间复杂度和常数级别额外空间的条件下完成。通过这样的交换操作,最终的数组结构使得第一个不在正确位置上的元素就是缺失的最小正整数。


合并区间

解题思路:先排序后遍历
1、首先,按照区间的起始位置对所有区间进行排序。
2、接着,遍历排序后的区间,依次合并重叠的区间。具体做法是比较当前区间的起始位置和上一个合并后区间的结束位置,如果发现有重叠,则合并;否则,将当前区间加入结果集。
3、最终得到的结果集即为合并后的不重叠区间数组。

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    if (!intervals || intervals.length === 0) {
        return [];
    }

    // 按照区间的起始位置进行排序
    intervals.sort((a, b) => a[0] - b[0]);

    const merged = [intervals[0]];

    for (let i = 1; i < intervals.length; i++) {
        const currentInterval = intervals[i];
        const lastMerged = merged[merged.length - 1];

        // 如果有重叠,则合并区间
        if (currentInterval[0] <= lastMerged[1]) {
            lastMerged[1] = Math.max(lastMerged[1], currentInterval[1]);
        } else {
            // 否则,将当前区间加入结果集
            merged.push(currentInterval);
        }
    }

    return merged;
};

轮转数组

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
function rotate(nums, k) {
    const n = nums.length;
    k %= n;  // 处理 k 大于数组长度的情况

    // 整体反转
    reverse(nums, 0, n - 1);

    // 前 k 个元素反转
    reverse(nums, 0, k - 1);

    // 后 n-k 个元素反转
    reverse(nums, k, n - 1);
}

// 反转数组的指定区间
function reverse(nums, start, end) {
    while (start < end) {
        const temp = nums[start];
        nums[start] = nums[end];
        nums[end] = temp;
        start++;
        end--;
    }
}

将数组元素向右轮转 k 个位置的思路是通过反转数组的不同部分来实现的。这种思路的来源可以追溯到翻转字符串的问题,其中我们也会使用类似的方法。

反转数组的思路:

1、整体反转: 先将整个数组进行反转,这样原来数组的末尾元素就变成了开头。
2、部分反转: 接着,对前 k 个元素进行反转,这样原来数组的开头 k 个元素就变成了末尾。
3、再次部分反转: 最后,对剩余的 n-k 个元素进行反转,将它们恢复到正确的顺序。
这样整个过程就相当于是将数组的元素向右轮转了 k 个位置。

为什么是执行三次反转呢?

在这里,执行三次反转是为了保持反转的方向,确保数组的元素正确地向右轮转 k 个位置。具体来说:

1、整体反转是为了将原数组的末尾元素移动到数组的开头。
2、第一次部分反转是为了将这些位于开头的元素移动到正确的末尾位置。
3、第二次部分反转是为了将原来的开头元素恢复到数组的正确位置。

这种思路的直观性在于,通过反转数组的不同部分,可以有效地实现循环移动的效果。这种方法在处理数组旋转等问题时非常常见。


238. 除自身以外数组的乘积

这道题目考察的是数组的乘积累积。解决这类问题的一种常见思路是使用前缀乘积和后缀乘积。

解题思路:
1、计算前缀乘积: 从左到右遍历一次数组,计算每个元素左侧所有元素的乘积。即 prefixProduct[i] 表示 nums[0]nums[1]…*nums[i-1]。
2、计算后缀乘积: 从右到左遍历一次数组,计算每个元素右侧所有元素的乘积。即 suffixProduct[i] 表示 nums[i+1]nums[i+2]…*nums[n-1]。
3、计算结果数组: 对于每个位置 i,结果数组中的元素就是 prefixProduct[i] * suffixProduct[i]。
下面是使用这种思路的示例代码:

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var productExceptSelf = function(nums) {
    const n = nums.length;

    // 计算前缀乘积
    const prefixProduct = new Array(n).fill(1);
    let product = 1;
    for (let i = 1; i < n; i++) {
        product *= nums[i - 1];
        prefixProduct[i] = product;
    }

    // 计算后缀乘积并同时计算结果数组
    let suffixProduct = 1;
    for (let i = n - 1; i >= 0; i--) {
        prefixProduct[i] *= suffixProduct;
        suffixProduct *= nums[i];
    }

    return prefixProduct;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值