双指针算法_复写零

文章讲述了如何使用双指针技巧,从右到左对数组中的0元素进行复写,同时处理数组长度限制和边界条件,确保不超出数组范围。通过示例和代码实现,详细解释了操作过程和边界处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

给一个固定长度的数组arr,将数组中出现的每一个0都复写一遍,并且将其余元素都往右移动

且不要再超过数组长度的位置写入元素,在数组上直接修改

示例:

双数组模拟操作: 

  • 从示例来看,因为数组的长度不能变动,导致右移动时,会把原来右边的元素挤出数组。
  • 在进行原数组修改之前,我们先进行双数组的模拟运算, 就如上图所示,开辟一个一模一样的数组空间,空间的首元素地址上方存在指针dest,而原数组的首元素地址上方摆放指针cur。
  • 当cur指针遇见非0元素时,将dest指向的空间位置拷贝该元素,随后dest和cur都同时向前移动。
  • 当cur遇见0元素时,dest指向的空间位置放入0元素,随后再向前一位,再度放置0元素,最后cur指针和dest指针向前移动。
  • 最后,dest移动到所在数组最后一个元素位置时,cur指针刚好移动到进行复写0操作后的最后一个存在于数组内部的元素。

操作总结: 

通过示例和双数组模拟操作的过程,我们可以得出结论,如果使用双指针从左到右进行0的复写,那么势必会覆盖数组原有的元素,所以双指针进行操作的方向必须改变,变成从右到左,从后向前! 

而如果往后向前操作,还是dest和cur一同出现再末端吗?

并不是,因为本题的示例中本就有右端的元素被挤出数组,所以直接将cur指针指向复写操作后数组的最后一个元素位置,而dest则指向数组的末端,直接进行元素的覆盖!

非0时直接将cur的元素交给dest,把dest指向的元素进行覆盖,而为cur指向0时dest指向的元素变成0并且往前移一步,再度把指向的元素变成0,随后cur和dest同时向前移动。

将cur指向的元素覆盖给dest指针指向的元素。 

复写操作后的最后一个元素的查询 :

继续使用双指针的操作,我们将dest定义为最后一个需要复写的位置,当前这里没有找到最后一个需要复写的位置,所以初始位置这里变成-1的位置,cur则是查看是0还是非0让dest向前1步还是两步。

cur遇到非0,dest向前一步!同时判断dest是否在数组的末端,不是则cur继续往前走! 

 当cur遇到0,dest向前移动两步,继续判断dest是否在数组末端,没有cur继续向前走!

因为cur是非0所以dest向前移动,然后dest抵达了数组的末端,随后结束得到了cur为最后一个复写的数 

操作总结:

处理边界问题: 

最后一个复写的数是0,且dest发生了越界!

所以要处理边界情况,当最后一个复写的数是0是,可能会把复写的数复写到外边!因为0复写两边!

处理:当最后一个是0时,必然会发送越界操作,因为我们是在查找到复写后的最后一个元素之后,进行直接返回操作,而遇到越界操作时,dest指针会跑到外面,所以拷贝的时候会出现错误。

所以为了处理这种问题,我们将dest前面一格的位置变成0,随后将cur指针向前移动一格,dest指针向前移动两格 

代码实现: 

 

<think>好的,我现在需要解决用户提到的“复写算法问题。这个问题要求在原数组上将所有元素复制一次,并保持非元素的相对顺序,同时不能使用额外的数组空间。用户之前给出的引用中有提到双指针技巧,比如引用[2]和[3],所以这可能是一个关键点。 首先,我得理解问题。题目是说,给定一个数组,遇到0就复制一次,后面的元素依次后移,但数组长度不变,超过的部分被舍弃。例如,数组[1,0,2,3]处理后变成[1,0,0,2]。需要原地操作,不能用额外空间。 那我应该怎么处理呢?如果直接从前向后遍历,每次遇到0就插入,这样后面的元素都要后移,时间复杂度会是O(n²),对于大数据量可能不够高效。所以可能需要更优的方法,比如双指针双指针的思路通常是使用两个指针,一个用于遍历,一个用于定位写入的位置。这里可能需要先确定处理后数组的有效长度,然后从后向前进行操作,避免覆盖未处理的元素。 比如,可以分两步走: 1. 第一次遍历,确定复写0后数组的实际长度,但因为原数组长度固定,需要找到最后一个可以保留的元素的位置。 2. 第二次遍历从后向前,根据第一次遍历找到的位置,将元素移动到正确的位置,遇到0则复写。 举个例子,假设原数组是[1,0,2,3,0,4]。第一次遍历时,需要计算可能的长度。比如,当遇到0时,长度会增加,但总长度不能超过原数组长度。所以可能需要从前往后模拟复写的过程,直到填满数组,从而确定哪些元素会被保留,哪些会被截断。 然后,第二次遍历时,从确定的最后一个位置开始,倒着将元素移动到数组末尾,遇到0时写入两次,并调整指针的位置。 需要注意边界情况,比如当最后一个元素是0时,是否需要复写,但可能超出数组长度,这时候可能不需要或者只能复写一次。 例如,原数组长度是4,元素是[1,0,2,3]。第一次遍历时,模拟复写: - 指针i开始遍历,遇到0时复写,此时需要检查是否超出数组长度。比如,假设原数组长度是4,当i走到某个位置时,复写后的长度超过数组长度,则需要截断。 具体步骤可能如下: 1. 初始化两个指针,top和i。top用来计算复写后的位置,i遍历原数组。 2. 当top超过数组长度时停止,此时i的位置可能需要调整,因为可能最后一个元素是0导致复写后超出长度。 3. 然后从i的位置倒着处理,将元素移动到数组末尾,遇到0则写入两次,否则正常写入。 这样,时间复杂度是O(n),空间复杂度是O(1),满足要求。 现在需要验证这个思路是否正确。比如,处理数组[1,0,2,3],正确结果是[1,0,0,2]。按照上述步骤: - 第一次遍历确定top的位置。初始top=-1,i=0。 - i=0时,元素是1,top增加到0。 - i=1时,元素是0,top增加2,变为2。 - i=2时,元素是2,top增加到3。 - i=3时,元素是3,此时top=3+1=4,超过数组长度3(索引从0开始),停止。此时i=3,但此时是否要检查最后一个元素是否需要复写? 可能需要调整,因为当top在增加时,可能刚好达到数组长度。例如,原数组长度是4,索引到3。当处理i=3时,top加上1变成4,等于数组长度(索引从0开始,长度是4的话索引最大是3)。这时候应该停止,i此时是3。 然后从i=3开始倒着处理,同时另一个指针j从数组末尾开始。 元素是3,所以直接放在j的位置,j减1。此时数组变成[1,0,2,3],但原数组可能已经被修改了?或者需要更仔细的处理。 这时候可能需要更多的细节。例如,当第一次遍历结束后,i的位置可能指向最后一个有效元素的位置,或者可能需要回退一步。 比如,当top超过数组长度时,i的位置可能需要回退,并且检查是否最后一个元素是0,导致top超过的情况。例如,在数组[1,0,2,3,0,4],假设原长度是6,处理后需要复写,但可能某些元素会被截断。 可能需要处理这样的情况:当在第一次遍历时,top的位置刚好超过数组长度,这时候最后一个元素如果是0,是否需要复写?比如,原数组是[0,0,0,0],处理后每个0都复写,但数组长度是4,所以处理后应该是[0,0,0,0]?因为每个0都被复写,但原来的四个0复写后变成八个,超过长度,所以需要截断。这时候第一次遍历需要正确计算可以处理的元素位置。 可能这个过程比较复杂,需要仔细处理边界条件。 总结步骤: 1. 使用双指针法,找到所有需要保留的元素的位置。 2. 从后向前遍历,将元素放置到正确的位置,遇到0则复写。 接下来,我需要将这些步骤转化为具体的代码实现,并测试几个例子确保正确。 例如,测试案例: 输入:[1,0,2,3,0,4],数组长度6。 正确输出应该是[1,0,0,2,3,0]。 第一次遍历: - i从0开始,top初始为-1。 - i=0,元素1,top +=1 → top=0。top <5。 - i=1,元素0,top +=2 → top=2。top <5。 - i=2,元素2,top +=1 →3 <5。 - i=3,元素3,top +=1 →4 <5。 - i=4,元素0,top +=2 →6,超过5。此时循环停止。此时i=4,top=6,超过数组长度5(索引0-5)。这时候需要调整,可能i的位置是4,但是此时复写这个0会导致top超过,所以只能处理到i=3的位置? 或者需要回退,当top超过时,检查是否最后一个元素是0,导致复写后超出,此时该0只能被处理一次而不是两次。 这部分可能需要更详细的逻辑,例如: 当top超过数组长度时,回退i的位置,并检查是否该元素是0,且此时是否需要只复写一次。 例如,在例子中,数组长度是6,索引0-5。当i=4时,元素是0,此时top加上2变为6,超过5,所以此时需要知道这个0是否会被复写。因为复写后需要两个位置,但此时只有最后一个位置可用。所以可能这个0只能复写一次,导致最后的位置被截断。或者这种情况是否可能发生? 可能需要处理这样的情况:当在第一次遍历中,top刚好超过数组长度,那么最后一个复写的0可能只能出现一次,而不是两次。 因此,在第一次遍历结束后,需要确定哪些元素会被保留,哪些会被截断。这时候可能需要调整i的位置,并检查是否在i位置上的元素是0,导致复写后超出数组长度,这时候这个0只能复写一次。 例如,当top超过数组长度时,i的位置可能需要回退到前一个位置,并且判断该元素是否是0,导致top超过。这时候,在第二次遍历时,处理到这个0的时候,只能复写一次,并且后面的元素可能被截断。 例如,数组是[0,0,0,0,0],长度5。处理后的结果应该是[0,0,0,0,0],因为每个0复写两次,但长度不够,所以最后一个0只能复写一次? 或者原数组长度为5的话,初始数组是[0,0,0,0,0]。第一次遍历时: i=0,元素0,top +=2 →2。top <4(数组长度是5,索引到4) i=1,元素0,top +=2 →4。top <4 → 不,等于4,继续。 i=2,元素0,top +=2 →6,超过4,停止。此时i=2,top=6。这时候需要回退到i=1的位置,因为最后一个0的复写导致top超过。但是原数组i=1的位置元素是0,复写两次后top是4,那么此时i=1时,处理到top=4,刚好在数组范围内。然后i继续增加到2,此时处理i=2的0,top+=2=6超过4,所以此时需要停止。此时i=2的位置的0无法完全复写,只能复写一次。因此在最终处理时,这个0只能复写一次,导致最终数组的最后一个位置是0? 这可能需要更仔细的调整。 看来这个问题中的边界条件较多,需要详细处理。 总的来说,双指针的思路是正确的,但实现时需要仔细处理这些边界条件,特别是当复写0时可能导致超出数组长度的情况。</think>### 复写算法实现方法 #### 问题分析 给定一个整数数组,要求将每个元素复制一次,并保持非元素的相对顺序。所有操作需在原数组上进行,不能使用额外空间。例如: - 输入:$[1,0,2,3,0,4]$ - 输出:$[1,0,0,2,3,0]$(最后一个因空间不足无法完全复写) #### 双指针解法 通过两次遍历实现: 1. **第一次遍历(模拟复写)**:确定哪些元素会被保留。 2. **第二次遍历(反向填充)**:从后向前写入数据,遇到复写。 ##### 步骤详解 1. **初始化双指针**: - `top` 标记复写后的逻辑终点位置 - `i` 遍历原数组 当 `top >= n-1` 时停止,此时 `i` 指向最后一个有效元素[^2][^3]。 2. **处理边界情况**: 若最后一次移动因导致溢出,需修正指针位置,确保最后一个复写一次[^2]。 3. **反向填充**: 从 `i` 开始倒序遍历,将元素写入 `j` 指针位置,遇到则额外复制一次。 ##### 时间复杂度 - 时间复杂度:$O(n)$ - 空间复杂度:$O(1)$ #### 代码实现 ```python def duplicateZeros(arr): n = len(arr) top = 0 i = 0 # 第一次遍历:确定保留元素的终点位置 while top < n: if arr[i] == 0: top += 2 else: top += 1 if top >= n: break i += 1 # 处理最后一个元素为导致溢出的情况 j = n - 1 if top > n: # 最后一次复写导致溢出 arr[j] = 0 j -= 1 i -= 1 # 反向填充 while j >= 0: if arr[i] == 0: arr[j] = 0 j -= 1 if j >= 0: arr[j] = 0 else: arr[j] = arr[i] j -= 1 i -= 1 return arr ``` #### 测试验证 1. **输入**:$[1,0,2,3]$ **输出**:$[1,0,0,2]$ 2. **输入**:$[0,0,0,0]$ **输出**:$[0,0,0,0]$(最后一个无法复写) 3. **输入**:$[8,4,5,0,0,0,0,7]$ **输出**:$[8,4,5,0,0,0,0,0]$ #### 核心逻辑解析 - **双指针模拟**:通过两次遍历避免元素覆盖问题,确保时间复杂度最优[^3]。 - **边界修正**:处理末尾溢出时,需调整指针位置以保证结果正确[^2]。
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值