力扣算法分析 27.移除元素

1. 题目描述

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

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

示例 1:

复制代码
输入:nums = [3, 2, 2, 3], val = 3
输出:2, nums = [2, 2, _, _]
解释:函数应该返回新的长度 2,并且数组的前两个元素均为 2。

示例 2:

复制代码
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0, 1, 3, 0, 4, _, _, _]
解释:函数应该返回新的长度 5,并且数组的前五个元素均为 0、1、3、0、4。

2. 题解

这道题目要求我们原地移除数组中所有等于 val 的元素。我们可以通过双指针的方法来优化解决。

算法思想:

我们使用双指针来解决这个问题。主要思路如下:

  • left 指针:指向当前数组中有效元素的末尾。

  • right 指针:用于遍历数组,检查每个元素是否需要保留。

具体的操作步骤是:

  1. 初始化

    • left = 0,指向数组的第一个位置,表示当前有效元素的最后一个位置。

    • right = 0,用于遍历整个数组。

  2. 遍历数组

    • 每次循环中,如果 nums[right] != val,则表示该元素不等于 val,我们应该保留它。此时将 nums[left] = nums[right],并且将 left 向右移动,表示下一个有效元素的位置。

    • 如果 nums[right] == val,则跳过该元素,不进行任何操作。

  3. 返回新数组的长度

    • 最终,left 就是数组中不等于 val 的元素个数。

通过双指针的方式,我们只需要遍历数组一次,时间复杂度是 O(n),而空间复杂度是 O(1),符合原地修改数组的要求。

代码实现:

java 复制代码
class Solution {
    public int removeElement(int[] nums, int val) {
        int n = nums.length;  // 获取数组的长度
        int left = 0;  // 初始化左指针,指向数组的起始位置

        // 右指针遍历数组
        for (int right = 0; right < n; right++) {
            // 如果当前元素不等于 val
            if (nums[right] != val) {
                nums[left] = nums[right];  // 将当前元素放到左指针的位置
                left++;  // 左指针右移,指向下一个位置
            }
        }
        return left;  // 返回新数组的长度
    }
}

3. 与本题算法相关的知识点

双指针技术

双指针是解决数组和链表类问题的一种常见技巧。通过两个指针分别从不同方向遍历或在同一方向上操作,可以高效地解决许多问题。在这道题中,双指针的核心思想是:

  • left 指向当前有效数组的最后位置,用来更新不等于 val 的元素。

  • right 用来遍历数组的每个元素,找出不等于 val 的元素,并把它移到 left 指针的位置。

双指针技术的优势是空间复杂度为 O(1),即我们在原地修改数组时,不需要额外的空间。这对于节省内存空间以及提高算法效率是非常有帮助的。

1. 左右指针的基本概念:

在编程中,特别是在数组和链表等数据结构中,"指针" 其实并不是指实际的内存地址,而是用来描述"某个位置"或"指向某个位置"的变量。

在 Java 中,我们没有直接的指针类型(不像 C/C++ 中的指针),但是我们可以使用变量(如 leftright)来模拟指针的行为,表示数组中元素的位置或下标。

2. 为什么 left 代表"当前数组中有效元素的最后位置"?

在这道题目中,不等于 val 的元素需要移到数组的前面,而且我们需要保持这些有效元素的顺序。

  • 我们用 left 来标记新数组(去除 val 后的有效元素)的"末尾"位置。随着遍历数组的进行,left 会不断向右移动,表示我们已经"填充"了数组的某些部分。
3. 左右指针是如何一起工作的:
right 指针:
  • right 用于遍历整个数组。它会依次访问每个元素。

  • 如果当前元素 nums[right] 不等于 val,我们就将这个元素保留下来,放到 left 指针的位置,然后将 left 指针向右移动。

left 指针:
  • left 作为有效元素的"末尾"位置,它开始时指向数组的第一个位置(即下标为 0)。

  • right 找到一个不等于 val 的元素时,left 就会被赋值为该元素,并且 left 之后会向右移动,准备接受下一个不等于 val 的元素。

通过这种方式,left 保持指向"新数组"最后一个有效元素的位置,也就是所有不等于 val 的元素都被移动到数组的前面

4. 具体例子来解释:

假设有如下数组:

java 复制代码
nums = [3, 2, 2, 3, 4]
val = 3

初始化时:

  • left = 0(指向数组的第一个位置)

  • right = 0(指向数组的第一个元素)

然后开始遍历:

  1. 右指针 right = 0 ,当前元素是 3,等于 val,跳过,不做任何操作。

  2. 右指针 right = 1 ,当前元素是 2,不等于 val,将 nums[left] = nums[right],即 nums[0] = 2,然后 left++,使得 left = 1

  3. 右指针 right = 2 ,当前元素是 2,不等于 val,将 nums[left] = nums[right],即 nums[1] = 2,然后 left++,使得 left = 2

  4. 右指针 right = 3 ,当前元素是 3,等于 val,跳过,不做任何操作。

  5. 右指针 right = 4 ,当前元素是 4,不等于 val,将 nums[left] = nums[right],即 nums[2] = 4,然后 left++,使得 left = 3

此时数组变成了:

java 复制代码
nums = [2, 2, 4, 3, 4]

left 最终指向的位置是 3,表示新数组的长度是 3,数组的前 3 个元素 [2, 2, 4] 是有效元素。

5. 为什么移到前面?

这个题目要求的是"原地"修改数组,意思是不能创建新数组,而是要在原数组上操作。因此,我们需要使用 left 指针来控制哪些位置应该填入有效元素。

  • 当我们发现一个不等于 val 的元素时,我们把它移动到数组的前部。left 作为指针,确保我们把这些有效元素填入数组的正确位置。

  • 所有原本在数组后面且等于 val 的元素,将被自然"丢弃"------因为 right 遍历整个数组时,它们不会影响 left 的位置。

原地修改数组

原地修改数组是指在不使用额外数组的情况下,对数组进行修改。在本题中,我们需要确保数组中所有不等于 val 的元素保留在原数组的前面,而不等于 val 的元素被移除。通过双指针的方式,我们将符合条件的元素直接移动到数组的前面,不需要额外开辟新的数组,符合原地修改的要求。

时间复杂度与空间复杂度

  • 时间复杂度:

    遍历数组一次,时间复杂度为 O(n),其中 n 是数组的长度。

  • 空间复杂度:

    我们只使用了常数级别的空间,即 O(1),因为我们没有创建新的数组,而是直接在原数组上进行操作。

4. 总结

这道题目通过使用双指针技术,成功解决了移除元素的问题。我们只需遍历一次数组,时间复杂度为 O(n),空间复杂度为 O(1),是一个高效的解决方案。掌握双指针技术能够帮助我们在处理数组、链表等问题时,避免不必要的空间开销,提高算法效率。

这类题目常见于面试和编程挑战中,熟练掌握双指针技巧对解决许多类似问题至关重要。

相关推荐
無限進步D1 天前
Java 运行原理
java·开发语言·入门
難釋懷1 天前
安装Canal
java
是苏浙1 天前
JDK17新增特性
java·开发语言
不光头强1 天前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp1 天前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多1 天前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood1 天前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员1 天前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai
IronMurphy1 天前
【算法三十九】994. 腐烂的橘子
算法