Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 06bc3c0

Browse files
committed
🐱(double-pointer): 27. 移除元素
重写题解
1 parent 1ec9e8f commit 06bc3c0

File tree

1 file changed

+83
-11
lines changed

1 file changed

+83
-11
lines changed

docs/data-structure/array/README.md

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -264,16 +264,84 @@ class Solution:
264264

265265
[原题链接](https://leetcode-cn.com/problems/remove-element/description/)
266266

267-
### 思路
267+
### 题意解读
268268

269-
双指针 i, j。
269+
划一下题目的重点:
270+
271+
- 原地移除
272+
- 不要使用额外的数组空间
273+
- 不需要考虑数组中超出新长度后面的元素
274+
275+
题目要求我们原地删除所有等于 `val` 的元素,不能使用额外空间,且**不用考虑删除后超出新数组长度后面的元素**
276+
277+
也就是说,如果原数组 `nums` 长度为 `x`,要删除的 `val` 元素个数为 `y`,那么我们只要**把这 `n` 个要删除的元素所在位置用其他有效元素覆盖掉**,然后返回最终的数组长度 `x - y`
278+
279+
**题目并非让我们真的删除数组的元素,而是要改写相关元素的值。**
280+
281+
### 思路阐述
282+
283+
那么要如何进行元素的改写呢?
284+
285+
既然要让 `val` 元素都堆在数组尾部,那么我们就派出一个开拓者探路,**只要遇到非 `val` 元素,就把它覆盖到前面来**
286+
287+
因此,我们可以定义两个指针:
288+
289+
- 快指针 `j`:用于寻找非 `val` 元素
290+
- 慢指针 `i`:当 `j` 找到非 `val` 元素时,就被非 `val` 元素覆盖
291+
292+
### 图解思路
293+
294+
以题中的 `nums = [3,2,2,3], val = 3` 为例。
295+
296+
开始时 `i``j` 都指向下标 0 位置:
297+
298+
![初始化时,i = 0, j = 0](https://user-gold-cdn.xitu.io/2019/11/21/16e8e78d6bf1eaee?w=425&h=179&f=png&s=8968)
299+
300+
此时 `j` 指向的元素为 `val`,所以把 `j` 右移动 1 位:
301+
302+
![把快指针 j 右移一位](https://user-gold-cdn.xitu.io/2019/11/21/16e8e79c1dd17340?w=425&h=179&f=png&s=8969)
303+
304+
此时,开拓者 `j` 找到了一个非 `val` 元素,那么就赋值给 `i` 吧:
305+
306+
![赋值得到新序列](https://user-gold-cdn.xitu.io/2019/11/21/16e8e95a2b497021?w=425&h=251&f=png&s=12570)
307+
308+
赋值以后,我们得到了一个新的序列 `[2, 2, 2, 3]`,我们可以得知:
309+
310+
- `j` 指向的元素一定不是 `val`
311+
- `i` 指向的元素也一定不是 `val`,因为它是从 `j` 指向的元素赋值得来的,`j` 指向非 `val` 元素才会进行赋值
312+
313+
这样一来,`i``j` 都完成了本轮使命,继续前进!
314+
315+
因此每次交换以后,我们都同步增长双指针,令 `i = i + 1``j = j + 1`
316+
317+
![同步增长双指针](https://user-gold-cdn.xitu.io/2019/11/21/16e8e97cf4ef1990?w=425&h=225&f=png&s=9812)
318+
319+
此时 `j` 又指向了一个非 `val` 元素,继续赋值:
320+
321+
![再一次赋值得到新序列](https://user-gold-cdn.xitu.io/2019/11/21/16e8e989fd262a29?w=425&h=246&f=png&s=12623)
322+
323+
因为本次 `i``j` 指向元素相同,所以赋值后序列没有改变。赋值操作后,我们继续同步增长双指针:
324+
325+
![同步增长双指针](https://user-gold-cdn.xitu.io/2019/11/21/16e8e9a17c6ecae5?w=425&h=227&f=png&s=9909)
326+
327+
此时 `j` 指向了一个 `val` 元素,无法进行赋值操作,继续增长 `j`,令 `j = j + 1`
328+
329+
![j 超出数组范围](https://user-gold-cdn.xitu.io/2019/11/21/16e8e9b72d7141a7?w=489&h=226&f=png&s=10105)
330+
331+
此时我们发现 `j` 超出数组范围了,循环结束。`[2, 2, 2, 3]` 即为我们最终所求结果,而红色部分即为新数组长度,长度为 `len(nums) - (j - i)`
332+
333+
### 总结一下
334+
335+
设置双指针 `i``j`,其中,`j` 用于寻找非 `val` 元素,来覆盖 `i` 所指向的元素。
336+
337+
- 初始时:设 `i = 0, j = 0`
338+
- 遍历数组:
339+
-`nums[j] != val`
340+
- 把 `j` 的值赋给 `i``nums[i] = nums[j]`
341+
- 同步增长双指针:`i = i + 1, j = j + 1`
342+
-`nums[j] == val`
343+
- `j` 变为快指针:`j = j + 1`,寻找下一个非 `val` 元素
270344

271-
- 初始:`i = 0, j = 0`
272-
-`nums[j] != val`
273-
- `nums[i] = nums[j]`
274-
- 同步增长双指针
275-
-`nums[j] == val`
276-
- j 变为快指针:`j = j + 1`
277345

278346
<!-- tabs: start -->
279347

@@ -319,9 +387,8 @@ func removeElement(nums []int, val int) int {
319387
// 去找一个不是 val 的值
320388
j++
321389
} else {
322-
// 互换
323-
nums[i], nums[j] = nums[j], nums[i]
324-
// i 在前进的过程中走的是 j 走过的路,一定不会再碰到 val
390+
// 赋值
391+
nums[i] = nums[j]
325392
i++
326393
j++
327394
}
@@ -333,6 +400,11 @@ func removeElement(nums []int, val int) int {
333400

334401
<!-- tabs: end -->
335402

403+
### 复杂度
404+
405+
- 时间复杂度:`O(n)`
406+
- 空间复杂度:`O(1)`,没有使用到额外空间。
407+
336408

337409
## 33. 搜索旋转排序数组
338410

0 commit comments

Comments
 (0)