Given an array nums and a value val, remove all instances of that value in-place and return the new length.
Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.
The order of elements can be changed. It doesn't matter what you leave beyond the new length.
Example 1:
Given nums = [3,2,2,3], val = 3, Your function should return length = 2, with the first two elements of nums being 2. It doesn't matter what you leave beyond the returned length.
Example 2:
Given nums = [0,1,2,2,3,0,4,2], val = 2, Your function should return length = 5, with the first five elements of nums containing 0, 1, 3, 0, and 4. Note that the order of those five elements can be arbitrary. It doesn't matter what values are set beyond the returned length.
Clarification:
Confused why the returned value is an integer but your answer is an array?
Note that the input array is passed in by reference, which means modification to the input array will be known to the caller as well.
Internally you can think of this:
// nums is passed in by reference. (i.e., without making a copy) int len = removeElement(nums, val); // any modification to nums in your function would be known by the caller. // using the length returned by your function, it prints the first len elements. for (int i = 0; i < len; i++) { print(nums[i]); }
题目解读
给定数组nums和值val,要求在数组内移除所有的val值,并返回新的长度。
规定:移动数组元素要在数组内进行,保证算法的空间复杂度在O(1)
在题目正文的最后放宽了对新数组的要求,可以不用保证新数组中的元素顺序,且不用在意超过新长度的数组部分是什么元素。(这直接影响了解题思路和优化思路)
解题思路
下面依次按照我的思路、优化思路、最佳思路的顺序阐述这道题的解法:
解法一(我的思路):
对于这种不允许用空间置换时间的题目,"two pointer" 的思想是一个非常重要的思路,可以有效解决时间复杂度太高的问题。结合题目要求进行思考后发现这种思想可以用于解题,大致思路如下:
- 令pre和hind为两个数组索引,初始值为0.
- pre用来记录第一个值为val的索引,hind为pre后第一个值不是val的索引。
- 当满足要求2时,对pre和hind所代表的值进行交换。
- 当hind到达数组末尾时,循环结束,新数组生成(我们可知pre和hind之间的索引对应的值都为val,所有为val的元素都移动到数组末尾后,题目要求即可满足)。
最后返回新长度时,重新从末尾开始遍历数组,直到第一个值不为val的位置。即可得到新的数组长度。
解法二:
解法一的思路满足了题目要求,并且时间复杂度已经到达最低,但是还是有显而易见的缺点:
- 没有利用好题目中的小要求:不需要在意超过新数组长度的部分的元素是什么。
- 最后还是要遍历一次数组才能得到新长度。
对于缺点1:既然我们已经得到了pre和hind之间的索引对应的值均为val,并且这之间的值一定会移动到数组末尾,不最为新数组的一部分,我们是否可以不交换pre和hind的值,而只是单纯的让nums[pre] = nums[hind]呢?答案是肯定的。
对于缺点2:如上所述,pre是第一个值为val的索引,当循环结束后,索引0至pre - 1之间的元素为新数组的元素,共有pre个,所以直接返回pre即可。代码段如下:
int j = 0;
int numLen = nums.length;
for (int i = 0; i < numLen; ++i) {
if (nums[i] != val) {
nums[j] = nums[i];
++j;
}
}
return j;
解法三:
对于{4, 3, 2, 1, 5}且val为5的数组,解法二的思路需要重复赋值4次,思考一下还可以继续优化吗?当然,因为还有一个条件没有使用,那就是不用保证新数组的元素顺序,也就是说,初始化时我们可以将两个索引放在数组开头和末尾,开头的索引用来寻找值为val的元素,一旦发现就将其交换到数组末尾,并且末尾的索引-1,这样当两个索引相遇时,题目就解答完毕了。
解法三代码:
class Solution {
public int removeElement(int[] nums, int val) {
int n = nums.length;
int i = 0;
while (i < n) {
if (nums[i] != val) {
++i;
} else {
nums[i] = nums[n - 1];
--n;
}
}
return n;
}
}
解法二代码:
class Solution {
public int removeElement2(int[] nums, int val) {
int j = 0;
int numLen = nums.length;
for (int i = 0; i < numLen; ++i) {
if (nums[i] != val) {
nums[j] = nums[i];
++j;
}
}
return j;
}
}
解法一代码:
class Solution {
public int removeElement(int[] nums, int val) {
int pre = 0, hind = 0;
int numLen = nums.length;
while (hind != numLen) {
nums[pre] = nums[hind];
if (nums[pre] != val) {
if (pre != hind) {
nums[hind] = val;
}
++pre;
}
++hind;
}
int valCount = 0;
for (int i = numLen - 1; i >= 0; --i) {
if (nums[i] == val) {
++valCount;
} else {
break;
}
}
return numLen - valCount;
}
}
如有错误,欢迎指摘。欢迎通过左上角的“向TA提问”按钮问我问题,我将竭力解答你的疑惑。