js算法题解(第三天)---LeetCode:283.移动零和15. 三数之和

297 阅读2分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

前言

每天至少一道算法题,死磕算法

题目

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

难度:简单

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

思路

解题一定要养成良好的习惯

第一步:从题目中提取关键信息

  • 1.保持非零元素的相对顺序,那就是非零元素的顺序不变
  • 2.必须在不复制数组的情况下原地对数组进行操作,那就是不允许开辟新的空间

第二步:看能不能和实际生活相联系

我们上一章把数组和一排的书进行联系,那么这一章我们依然把数组和一排的书相联系

这个题就相当于我们的书架上,排序好的书,但是中间有空隙,现在你想把所有的书向前挪动,把空隙留到最后,你会怎么做呢,答案很简单呀,你把所有的书向前推,那么后面自然是留出了空隙

所以这道题就很简单了

var moveZeroes = function (nums) {
    // 设置下标位,这是利用下标发,常用语重新确定位置的时候
    let length = 0;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] > 0) {
            nums[length] = nums[i];
            length++;
        }
    }

    for (let j = length; j < nums.length; j++) {
        nums[j] = 0;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

总结

接下来我们先来总结一下我们做数组算法题的模板,你面试遇到问题,就往这些公式上套就行了,就像我们高考做数学题套公式一样

  • 1.下标法,常用于更换数组元素的位置

    例如我们解题中常常在题目头部设置 let length = 0;

  • 2.双指针法,前提必须是数组有序

  • 3.map大法,可用于要返回数组的索引

有些同学会问,你上一章不是说过,只要数组就可以往双指针方法上面套么,那么两数求和方法能不能往双指针方法上面套呢

答案是不能的,因为两数之和的答案让我们返回两个数的下标,可是我们使用双指针的前提是数组必须有序,所以排序以后,返回的数组下标就不正确了,所以这个要用map大法了

接下来我们再来讲一道综合性比较强的问题

我们来讲一下三数之和,先来看题

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

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

难度:中等

示例 1:

输入:nums = [-1,0,1,2,-1,-4]

输出:[[-1,-1,2],[-1,0,1]]

思路

第一步:从题目中提取关键信息

  • 1.答案中不可以包含重复的三元组,也就是必须要去重,过滤掉重复的元素

第二步:思路

我们之前讲过,任何求和的问题,都可以转化成求差的问题,所以三数之和问题可以转化为固定一个数+两数求和问题

双指针的前提是排序,那么排完序的数组,让三个数加起来为零,那么就不用判断零右边的数了,因为是不会等于零的

双指针遍历流程

令左指针 left = j + 1,右指针 right = nums.length - 1,当 left<right时,执行循环:
当 和为0的时候,执行循环,判断左界和右界是否和下一位置重复,去除重复解。并同时将 left,right 移到下一位置,寻找新的解
若和大于 0,说明 nums[right] 太大,right 左移
若和小于 0,说明 nums[left] 太小,left 右移

所以这个问题就很好做了,而且这个两数求和问题不是返回下标,所以我们还可以用双指针来做

var threeSum = function(nums) {
    // 最终结果
    let result = [];
    // 要使用双指针所以先排序
    nums = nums.sort((a,b)=>{
        return a-b;
    })
    for(let i =0;i<nums.length;i++){
        // 如果nums[i]大于0的话,就不用遍历了
        if(nums[i]>0){
            break;
        }
        // 重复的跳过
        if(i>0&&nums[i]===nums[i-1]){
            continue;
        }

        // 左指针
        let left = i+1;
        let right = nums.length-1;
        // 两数之和
        let sum = 0 - nums[i];
        while(left<right)
            // 如果两数之和大于sum,right--
            if(nums[left]+nums[right]>sum){
                right--;
            }else if(nums[left]+nums[right]<sum){
                left++;
            }else{
                // 结果千万要写到left++,right--之前
                result.push([nums[i],nums[left],nums[right]]);
                left++;
                right--;
                // 跳过重复,注意这里防止重复是while
                while(left<right&&nums[left]===nums[left-1]){
                    left++;
                }
                while(left<right&&nums[right]===nums[right+1]){
                    right--;
                }
        }
    }
}

return result;

};

这个问题我觉得最大的难点,就在防止重复,所以我们可以总结出来

排序以后,把当前数和前一个数作对比,只要相同就过滤掉

  • 时间复杂度:O(n^2), 数组遍历O(n),双指针遍历O(n),所以时间复杂度为O(n^2)
  • 空间复杂度:O(1), 指针使用常数大小的额外空间

那今天就到此结束,明天我们继续分享

参考: