【双指针法】LeetCode经典例题27移除元素及相关例题(26、283、844、977)解题思路及代码

27.移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

解题思路

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。

代码详解

var removeElement = (nums, val) => {
    
    
    let k = 0;
    for(let i = 0;i < nums.length;i++){
    
    
        if(nums[i] != val){
    
    
            nums[k++] = nums[i]
        }
    }
    return k;
};

复杂度分析

时间复杂度:O(n)
空间复杂度:O(1)

相关例题

26.删除有序数组中的重复项

给你一个 升序排列的数组 nums ,请你 原地 删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。

由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。

将最终结果插入 nums 的前 k 个位置后返回 k 。

不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

示例 2:

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

思路

因为给定数组nums为升序排列,即相同元素下标一定连续,所以

  • 数组为空时,return 0
  • 数组不为空时,比较nums[fastIndex]nums[fastIndex-1],如果相等,跳过;如果不等,将nums[fastIndex]赋值给numsslowIndex]slowIndex++,遍历完成return slowIndex即可

代码

var removeDuplicates = function(nums) {
    
    
    if (nums == null) {
    
    
        return 0;
    }else{
    
    
        let slowIndex = 1, fastIndex = 1;

        while (fastIndex < nums.length) {
    
    
            if (nums[fastIndex - 1] != nums[fastIndex]) {
    
    
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
            fastIndex++;
        }
        return slowIndex;
    }
};

283. 移动零

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

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

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

示例 2:

输入: nums = [0]
输出: [0]

思路
nums[fastIndex] != 0为判断条件,最后再将剩余位置全部覆盖为0
代码

var moveZeroes = function(nums) {
    
    
    let slowIndex = 0;
    for (let fastIndex = 0; fastIndex < nums.length; fastIndex++) {
    
    
        if (nums[fastIndex] != 0) {
    
    
            nums[slowIndex] = nums[fastIndex]
            slowIndex++;
        }
    }
    for (let i = slowIndex; i < nums.length; i++) {
    
    
       nums[i] = 0;      
    }
    return slowIndex
};

844. 比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

注意:如果对空文本输入退格字符,文本继续为空。

示例 1:

输入:s = "ab#c", t = "ad#c"
输出:true
解释:s 和 t 都会变成 "ac"

示例 2:

输入:s = "ab##", t = "c#d#"
输出:true
解释:s 和 t 都会变成 ""

示例 3:

输入:s = "a#c", t = "b"
输出:false
解释:s 会变成 "c",但 t 仍然是 "b"

思路
自己没什么思路,参考官方解析
一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉。

具体地,我们定义 skip 表示当前待删除的字符的数量。每次我们遍历到一个字符:
若该字符为退格符,则我们需要多删除一个普通字符,我们让skip 加 1;
若该字符为普通字符
skip 为 0,则说明当前字符不需要删去;
skip 不为 0,则说明当前字符需要删去,我们让 skip 减 1。

这样,我们定义两个指针,分别指向两字符串的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或遍历完字符串为止。

代码

var backspaceCompare = function (s, t) {
    
    
    // return isSame(s)==isSame(t)
    let i = s.length - 1;
    let j = t.length - 1;
    while (i >= 0 || j >= 0) {
    
    
        // 跳过#字符
        if (s[i] == '#') {
    
    
            let skip = 1;
            while (i >= 0 && s[--i] == '#') skip++;
            // 删除过程中,如果遇到#
            while (skip != 0) {
    
    
                i--;
                if (s[i] == '#') skip++;
                else skip--;
            }
        }
        if (t[j] == '#') {
    
    
            let skip = 1;
            while (j >= 0 && t[--j] == '#') skip++;
            while (skip != 0) {
    
    
                j--;
                if (t[j] == '#') skip++;
                else skip--;
            }
        }
        // 实现比较
        if (s[i] == '#' || t[j] == '#') continue;
        else {
    
    
            if (s[i] != t[j]) return false;
            i--;
            j--;
        }
    }
    if (i < 0 && j < 0) return true;
    else return false;
};

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

思路
重点:非递减顺序,所以平方最大值只可能出现在最左边或者最右边
定义一个新数组arr存放平方值,双指针法:i指向数组开始,j指向数组结束,比较数组最左与最右值大小,将较大数存进arr尾,较大数边指针向中移动
代码

var sortedSquares = function(nums) {
    
    
    let n = nums.length;
    let arr = [];
    let i = 0, j = n - 1, k = n - 1;
    while (i <= j) {
    
    
        let left = nums[i] * nums[i], right=nums[j] * nums[j];
        if (left < right) {
    
    
            arr[k] = right;
            k--;
            j--;
        }else{
    
    
            arr[k] = left;
            k--;
            i++;
        }
    }
    return arr;

};

复杂度分析

时间复杂度:O(n),其中 nn 是数组 nums 的长度。
空间复杂度:O(1)。除了存储答案的数组以外,我们只需要维护常量空间。

总结

数组中的元素为什么不能删除,主要是因为:
数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。

猜你喜欢

转载自blog.csdn.net/yolo_______95/article/details/127459442