前言
大学毕业以来一直想提升自己数据结构与算法相关的知识(大学没有学好=。=),后来偶然的机会在网上看到LeetCode,觉得很不错,因此就打算通过LeetCode来练习算法相关的一些知识和能力,活跃自己思维,锻炼自己的编程能力。
但是学习这个过程知识阅读、记忆和短期的练习是不够的,还需要总结与复习的过程,才能真正把知识变成自己的东西(自己十几年学习总结的拙见=。=)。所以就想着通过系列技术博客的形式记录自己的学习过程,因为自己是一个前端开发程序猿,因此编程语言使用JavaScript。
目前打算总结的有如下几点:
- 题目涉及的数据结构知识点
- 自己的解题思路
- 分析别人的解题思路(LeetCode上可以在自己提交答案后查看他人的答案)
- 总结
那么开始我的第一次算法练习总结。
题目描述:
给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,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
你不需要考虑数组中超出新长度后面的元素。
解题思路:
要想正确的解题我们首先要准确的理解题意(我一开始就理解错了)。
首先,这里的多次强调要在原地删除或者原地修改,因此我们只能对nums数组进行操作,不能再声明额外的数组或者对象。
其次,这里因为这个删除的字眼导致了当时的理解错误,我以为是真的必须要删除掉重复出现的元素生成一个不包含重复元素的数组,实际上我们看了下面的例子就能明白,这里并不是必须要删除掉nums中重复的元素,我们需要的是将不重复的数数组元素排在数组的前面,并返回不重复数组元素的长度(实际上使用我们重新生成的数组和不重复元素的长度我们也就可以轻易的得到一个不重复数组了)。
还有一点是这里说明了是一个有序数组,我当时是按照无序数组做的,最后看别人的答案时还以为别人的不对呢=。=。
那么接下来就说一下我的解题思路:
我首先想到的就是最笨的方法,两次遍历数组,即将数组中的每个元素与它之后的每个元素比较,如果有与它相同的元素,那么使用js数组的splice方法将重复的元素删除,代码如下。
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
for(let i=0;i<nums.length;i++){
for(let j=i+1;j<nums.length;j++){
if(nums[j]===nums[i]){
nums.splice(j,1);
}
}
}
return nums.length;
};
但是执行之后发现结果并不正确。
假设nums = [0,0,1,1,1,2,2,3,3,4],
我们的执行上述代码后的结果是[ 0, 1, 1, 2, 3, 4 ]
为什么呢?
通过断点调试我们发现第三个1并没有被我们遍历到,原因是当我们删除了每个元素时,其后的元素的指针会依次向前移动,即减1,但是我们并没有改变指针的值,因此会有一些元素是遍历不到的。
以上面的程序为例,我们首先删除了指针为1的元素0,这个是其后的元素会依次向前平移(指针减1),这时它们的排列是[0,1,1,1,2,2,3,3,4],这时原来指针为2的1,现在它的指针变为1。
我们这时拿指针为1的元素1与后面的每个元素比较,我们会删除掉指针为2的元素1。
[0,1,1,2,2,3,3,4]
这时候问题出现了,原来指针为3的元素1由于前一个元素被删除指针前移导致了它的指针变成了2,而这是我们的用来遍历的变量指针j的值并没有相应的变化,依然会继续递加指向指针为3的元素2,这是我们就"巧妙"的错过了一个重复的元素1=。=。
那么怎么解决这个问题呢,上面的思路中有提到,是由于删除元素后变量指针j没有跟随指针变化导致的这个问题,那么我们只需要在每次删除一个数组元素是执行j--即可,即将j的值减1,代码如下。
for(let i=0;i<nums.length;i++){
for(let j=i+1;j<nums.length;j++){
if(nums[j]===nums[i]){
nums.splice(j,1);
j--;
}
}
}
还有一种解决方法就是从后向前遍历i之后的元素,也可以避免指针移动导致的问题,代码如下。
for(let i=0;i<nums.length;i++){
for(let j=nums.length-1;j>i;j--){
if(nums[j]===nums[i]){
nums.splice(j,1);
}
}
}
这个就是我当初提交的答案了。但是执行时间为300ms-400ms左右 。执行时间很长,因此就去看了一下别人的提交的答案,然后惊奇的发现很多同学并没有真正的删除掉重复元素,而是将不重复元素一次排列在数组的前面。有的在我看来还是错的,因为执行后还是有排列在前的还是有重复元素。
后来发现是自己没有准确的理解题意,首先是忽略了有序数组的前提,其次是没有理解原地修改元素的说法。
其他思路:
我们主要来看一个在我看来较为优秀的解法,先上代码:
var removeDuplicates = function (nums) {
let count = 0;
for (let i = 1; i < nums.length; i++) {
if (nums[count] !== nums[i]) {
count += 1;
nums[count] = nums[i];
}
}
return count+1;
};
实际上我们如果精确的理解了题意之后理解上面的代码很简单,首先我们知道我们要处理的数组是一个有序的数组,所以才可以使用上面的程序解决。那么我们来讲一下上面这段程序的解题思路(我看有人称这种方法双指针法)。
首先这是一个有序数组,我们只需要声明一个变量来做为新数组的指针,那么这个指针肯定是从0开始的,我们只需要在遍历一遍原数组,在遇到一个与当前最靠后的新数组指针(即当前count的值)指向的数组元素不相等的值时我们就将指针count加1,并将该原数组的元素赋值给新数组的指针count+1的元素。这样,我们只需要遍历一遍数组,既可以得到所需新数组的长度。
我们可以看出,这个程序实际上并没有删除掉所有的重复元素,但是我们知道,我们知道了不重复数组的元素那么借助数组的splice方法我们可以很简单的得到所需的新数组。
总结:
首先,以后解题时首先要准确的理解了题意再开始解题=。=,
其次,注意删除数组时数组指针的移动。
最后,我认为此题目就是在指引我们思考在解决有序数组去重方面建立一种新的高效的解决思路,我们不是在发现重复元素即删除的方法,而是在对数组元素重新赋值,获得新数组之后整体切割。
如有其它高见,还望不吝赐教=。=、