今天依旧是双指针类型的题目,大家做的时候重点体会方法,不能一昧的使用暴力解法。
我后面会更新一些算法题,C++知识(基础知识,STL,C++11等)和Linux等内容,敬请期待。
双指针简介
常见的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。
对撞指针:⼀般用于顺序结构中,也称左右指针。
• 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼近。
• 对撞指针的终止条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循环),也就是:
◦ left == right (两个指针指向同⼀个位置)
◦ left > right (两个指针错开)
快慢指针:又称为龟兔赛跑算法,其基本思想就是使用两个移动速度不同的指针在数组或链表等序列结构上移动。
这种方法对于处理环形链表或数组非常有用。
其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使用快慢指针的思想。
快慢指针的实现方式有很多种,最常用的⼀种就是:
• 在⼀次循环中,每次让慢的指针向后移动⼀位,而快的指针往后移动两位,实现一快一慢。
复写零
给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。
注意:请不要在超过该数组长度的位置写入元素。请对输入的数组就地进行上述修改,不要从函数返回任何东西。
示例 1:
输入: arr = [1,0,2,3,0,4,5,0]
输出: [1,0,0,2,3,0,0,4]
解释:
调用函数后,输入的数组将被修改为: [1,0,0,2,3,0,0,4]
题目说的意思就是写个函数,该函数需要在给定的数组上进行操作,不能额外自己建一个数组。然后从下标为0开始遍历数组,如果遇到数组元素值为0,那就把这个元素后边的数据全部向后挪一位,空开一个位置,把这个空位置填充上0。要注意的是数组大小是固定的,既然我们把0复写了一遍,那就注定有些元素得舍弃。
例如输入: arr = [1,0,2,3,0,4,5,0] 输出: [1,0,0,2,3,0,0,4]我们将第一次出现的0和第二次出现的0复写之后,最后一个元素是4就已经达到数组容量了,所以后面的元素5和0就舍弃不要了。
考虑到在复写操作的时候需要为复写的0挪空间,如果我们从前向后进行原地复写操作,每次挪空间都得从后边开始挨个挪。所以我们不如直接从后向前开始,但这里有个问题,那就是你要从后往前复写,你先得找到哪个元素是最后吧,你连复写到哪个元素正好道达数组的最大容量都不知道,怎么从后往前呢?
因此我们大体分为两部分:
i. 先找到最后一个复写的数;
ii. 然后从后向前进行复写操作。
找最后一个复写的数
那如何找到最后一个数呢,先初始化两个指针:cur=0,dest=-1.我们可以根据cur位置的值来找,如果cur位置的值是0,那么dest+=2,否则的话dest++
初始状态
非0,dest++;cur++;
为0,dest+=2;cur++;
非0,dest++;cur++;
非0,dest++;cur++;
为0,dest+=2;cur++;
非0,dest++;
至此找到了边界就是数值为4的下标为[5],此时dest正好达到下标为n-1处(n为数组中元素个数,n-1就是指数组的最大下标,因为数组下标是从0开始的,所以数组中元素个数为n,那他的下标就是从[0,n-1])。那如果这个例子中下标为[5]的地方不是4而是0呢?那是不是dest就指向下标为n的地方了,所以最终dest有两种可能,一种是指向下标为n-1处,另外一种是指向下标为n处,这里要进行边界处理。
以下是这部分能代码实现:
int cur=0,dest=-1,n=arr.size();
while(cur<n)
{
if(arr[cur]) dest++;
else dest+=2;
if(dest>=n-1) break;
cur++;
}
处理边界
就是上面讨论过的,如果最后dest等于n的话那说明数组的最后一个元素一定是0,因此作此处理。(可以理解为dest每次加一那就是非零,每次加2的地方那就是应该放0)
if(dest==n)
{
arr[n-1]=0;
dest-=2;
cur--;
}
从后向前完成复写操作
判断如果cur处是非零元素,那么就将该值赋值给arr[dest],然后dest–,此处合并为了arr[dest–]=arr[cur]。要是零元素的话,那就两次将arr[dest]=0;dest–,此处合并为了两次arr[dest–]=0。
while(cur>=0)
{
if(arr[cur]) arr[dest--]=arr[cur];
else
{
arr[dest--]=0;
arr[dest--]=0;
}
cur--;
}
注:如果有只学过C语言没学过C++的朋友这部分也可以看,题目中有个这个东西:vector& arr,你们把他当作数组就行了,其实就是个int arr[]。然后代码中有一步是n=arr.size();这一步就是把数组中元素个数算出来然后赋值给n,你们使用C语言可以用sizeof来算一下数组中元素个数。
C++完整代码:
class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int cur=0,dest=-1,n=arr.size();
while(cur<n)
{
if(arr[cur]) dest++;
else dest+=2;
if(dest>=n-1) break;
cur++;
}
if(dest==n)
{
arr[n-1]=0;
dest-=2;
cur--;
}
while(cur>=0)
{
if(arr[cur]) arr[dest--]=arr[cur];
else
{
arr[dest--]=0;
arr[dest--]=0;
}
cur--;
}
}
};
class Solution {
public void duplicateZeros(int[] arr) {
int cur = 0, dest = -1, n = arr.length;
// 1. 先找到最后⼀个需要复写的数
while (cur < n) {
if (arr[cur] == 0)
dest += 2;
else
dest += 1;
if (dest >= n - 1)
break;
cur++;
}
// 2. 处理⼀下边界情况
if (dest == n) {
arr[n - 1] = 0;
cur--;
dest -= 2;
}
// 3. 从后向前完成复写操作
while (cur >= 0) {
if (arr[cur] != 0)
arr[dest--] = arr[cur--];
else {
arr[dest--] = 0;
arr[dest--] = 0;
cur--;
}
}
}
}
时间复杂度 :虽然我们有两个循环,但是常数级别的时间还是可以忽略的,所以时间复杂度还是O(n)。
空间复杂度 :只用了有限的几个变量所以是O(1)。