leetcode
leetcode 1 ~ 50
移除元素

移除元素

难度:

标签:

题目描述

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

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

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

 

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

 

示例 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,3,0,4]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

 

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

代码结果

运行时间: 36 ms, 内存: 14.9 MB


/*
 * 题目思路:
 * 使用Java Stream API,我们可以通过过滤出不等于给定值的元素,
 * 然后将这些元素重新放回原数组的前部分,并返回新的长度。
 */
 
import java.util.Arrays;
 
public class Solution {
    public int removeElement(int[] nums, int val) {
        // 过滤出不等于目标值的元素并收集到新的数组
        int[] newArray = Arrays.stream(nums)
                                .filter(num -> num != val)
                                .toArray();
        // 将新数组的元素复制回原数组
        System.arraycopy(newArray, 0, nums, 0, newArray.length);
        return newArray.length; // 返回新数组的长度
    }
}

解释

方法:

这个题解使用了双指针的思路。指针i用于记录新数组中不等于val的元素的位置,指针j用于遍历原数组。遍历过程中,如果当前元素nums[j]不等于val,就将其复制到nums[i]的位置,然后i向右移动一位。最后,i的值就是新数组的长度。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
为什么选择在元素不等于`val`时才移动指针`i`?在算法设计中,这种决策背后的逻辑是什么?
在这个算法中,指针`i`的作用是记录新数组中不包含值`val`的元素的位置。当遇到一个元素不等于`val`时,意味着这个元素需要被保留在新数组中。因此,将其复制到`i`指向的当前位置,然后移动`i`,以便下一个符合条件的元素可以被复制到下一个位置。这种决策确保了只有符合条件的元素被保留,同时`i`的最终位置提供了新数组的长度。这样的设计简洁且高效,避免了不必要的元素交换或额外空间的使用。
🦆
在原数组中修改后,数组的后部分(超出新长度的部分)还包含原始数据,这会对实际应用产生什么影响?是否有必要清除或重置这部分数据?
在原数组中修改后,超出新长度的部分确实会包含原始数据的剩余部分,这可能会导致数据的冗余或误解。在实际应用中,如果只关心数组的新长度内的数据,剩余部分通常可以忽略。但是,如果环境中对数据的安全性有较高要求,或者避免数据泄露是一个考虑因素(例如在处理敏感信息时),清除或重置这部分数据是必要的。这可以通过将超出新长度的部分设置为`null`或其他无效数据来实现。
🦆
该算法在返回的新长度小于原数组长度时,如何确保不会访问到剩余未处理的元素?
该算法通过返回新长度`i`来确保外部调用者不会访问到剩余未处理的元素。在实际使用中,调用者应当根据返回的长度`i`来处理或访问数组。例如,可以通过循环从`0`到`i - 1`来访问或处理数组元素。这种方式确保了只有经过处理,并且确定不包含值`val`的元素被访问,从而避免了对原数组未处理部分的访问。
🦆
在实际的编程实现中,如何测试这种类型的函数以确保其健壮性和正确处理边界情况?
在测试这种类型的函数时,应考虑多种边界和特殊情况:1. 测试空数组的情况,确保函数返回0且不产生错误。2. 测试所有元素都等于`val`的情况,确保返回长度为0。3. 测试没有任何元素等于`val`的情况,确保返回长度等于原数组长度。4. 测试`val`仅出现在数组开头、结尾或中间的情况,以确保算法的准确性。5. 考虑重复元素和大数据量的情况,以测试性能和效率。通过这些综合测试,可以确保函数在各种情况下都能正确运行,并处理各种边界情况。

相关问题

删除有序数组中的重复项

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

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k 。

判题标准:

系统会用下面的代码来测试你的题解:

int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有断言都通过,那么您的题解将被 通过

 

示例 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 。不需要考虑数组中超出新长度后面的元素。

 

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按 非严格递增 排列

移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

 

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

 

提示:

  • 列表中的节点数目在范围 [0, 104]
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

移动零

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

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

 

示例 1:

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

示例 2:

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

 

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

 

进阶:你能尽量减少完成的操作次数吗?