【双指针算法】--- 移动零 && 复写零

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     算法Journey


本篇博客我们讲解一下双指针算法的应用,双指针算法在做数组相关的题比较常用,下面开始我们今天的学习~


🏠 移动零

📒 题目解析

题目链接:283. 移动零 - 力扣(LeetCode)

题目内容:

这道题目是给我们一个数组,我们需要将数组划分两个部分,一个部分是非0元素,另一部分都是0,同属还要注意不能复制数组要原地操作,同时非0元素在移动后顺序要与移动前相同,比如示例1中1和3在移动完成后1还是要在3的前面

📒 算法原理

思路1:

1.首先定义两个指针target用来找非0的数,dest用来找0

2.在target不越界的情况下如果target的位置比dest大我们就对两者位置的数进行交换,交换完后target继续遍历扫描,而dest要注意如果交换完后位置还是0就让其不动,反之++

3.如果target的位置比dest小就不需要交换,我们只需target++让他继续扫描

动图演示:

参考代码:

class Solution { 
public:
    void moveZeroes(vector<int>& nums)
    {
        int target = 0; //找非0的指针
        int dest = 0;//找0的指针
        if(nums.size() == 1)
          return;
        while (target < nums.size() )
        {
            //先找非0的数
            while (target < nums.size() && nums[target] == 0)
            {
                target++;
            }
            //找0
            while (dest < nums.size() && nums[dest] != 0)
            {
                dest++;
            }
            //交换并更新指针
            if (target < nums.size() && dest < nums.size()&& target > dest)
            {
                int temp = nums[dest];
                nums[dest] = nums[target];
                nums[target] = temp;
                //更新指针位置
                if (nums[dest] != 0)//这样才能保证相对顺序
                    dest++;
                target++;
            }
            else
            {
                target++;//target继续遍历
            }
        }

    }
}s;

这种思路主要是一个指针遍历整个数组找到非0元素,另一个指针紧随其后找0,满足题目条件就进行交换,直到遍历完为止,但是细节较多且有点混乱我们从另一个角度看看。

思路2:

我们最终的目的是将数组划分为两块,以dest为分界点,dest及其左半部分都是非0元素,dest右边的都是0.

因此我们仍然采用两个指针:

1.cur指针用来遍历整个数组,dest作为一个分界点

2.当cur遇到0元素时,cur++继续移动因为此时(dest,cur-1]都是0

3.当cur遇到非0元素时,要将非0元素并入[0,dest]这个区间,也就是dest++给他一个位置再交换,cur继续移动++

动图演示:

参考代码:

class Solution { 
public:
    void moveZeroes(vector<int>& nums)
    {
        int cur = 0;//从零下标的开始待处理 所以dst下标是-1
        int dest = -1;
        while(cur < nums.size())
        {
          if(nums[cur] == 0)
          {
            cur++;
          }
          else
          {
            dest++;//dest++是为了让非0的数并入[0,dest]的区间
            swap(nums[dest],nums[cur]);
            cur++;
          }
        }

    }
}s;

这样是不是就很清晰了

总结:对于类似这种数组划分区间/数组分块的问题可以考虑用双指针

🏠 复写零

📒 题目解析

题目链接:1089. 复写零 - 力扣(LeetCode)

题目内容:

这道题目要求我们对一个数组中的0复写一遍,但要注意只能原地修改,返回数组是没用的;同时并不是复写前数组中每个0都要复写,如果复写到超出原数组长度就停止!

📒 算法原理

思路1:

1.找0:首先我们需要定义一个cur指针遍历数组,找到0就停下来复写

2.复写:到0的位置,我们利用迭代器在该位置的下一个位置进行复写(插入0)并更新指针

3.删除:复写完成后可能有超出size的部分,这时需要我们进行删除

注意点:

1.在找0时,如果遇到类似【1,2,3】这样的例子,可能会越界,所以我们需要加上一个限制条件

2.在复写时,如果插入0的位置合法,我们正常插入;如果不合法(比如 【1 2 3 4 0】),我们仍然需

要更新cur指针使它遍历完数组

3.在复写找0后的插入位置时,用find我们要注意从cur位置处往后找,因为前面可能有相同的数字

4.删除过程判断:将迭代器更新到begin() + size - 1位置,再判断复写后size是否变化,有变化

删除数据

动画演示:

参考代码:

class Solution {
public:
    void duplicateZeros(vector<int>& arr)
    {
        int cur = 0;
        int oldsize = arr.size();
        vector<int>::iterator it = arr.begin();
        
        while (cur < oldsize)
        {   //找0
            while (cur < oldsize && arr[cur] != 0)
            {
                cur++;
            }
            //复写过程
            if (cur + 1 < oldsize)//可能造成死循环所以要加上else 比如1 1 2 4 0最后一个是0
            {
                it = arr.begin() + cur;
                it = std::find(it, arr.end(), arr[cur + 1]);//注意要从cur的后面开始找 前面可能有一样的数字
                it = arr.insert(it, 0);
                cur += 2;
            }
            else
            {
                cur++;
            }
        }
        it = arr.begin() + (oldsize - 1);//判断是否增加了数字 多的要删除
        //删除过程
        if (arr.size() - oldsize > 0)
          arr.erase(it + 1, arr.end());
    }
}s1;

这种思路比较容易想到,但是要注意细节(越界,边界情况,迭代器更新等),一不小心就容易出错,下面提供第二种思路

思路2:

如果我们跟移动零一样从左往右进行复写呢?

很明显我们发现从左往右会导致部分数据被覆盖,因此我们选择从右往左复写

从后往前就很好地解决了覆盖的问题,问题是怎么确定最后一个复写的数,这里又要用到我们的双指针算法

这里的原理主要是利用一个指针cur确定这个数要复写几次,另一个指针dest移动次数代表了这个数复写的次数,复写到size() - 1为止,这样就很好地模拟了这个过程,实在巧妙!但是我们还要注意边界情况!

参考代码:

class Solution {
public:
    void duplicateZeros(vector<int>& arr)
    {
        int cur = 0;
        int dest = -1;
        //找最后一个复写的数 dest到达size() - 1就停止
        while(dest != arr.size() - 1)
        {
            
             if(arr[cur])
             {
                dest++;
             }
             else
             {
                if(dest == arr.size() - 2) //边界情况
                  {
                    arr[arr.size() - 1] = 0;
                    cur--;
                    break;
                  }
                 else
                   dest+=2; 
             }
             if(dest < arr.size() - 1)
               cur++;
        }
    //复写过程 从后往前
      while(cur >= 0)
      {
          if(arr[cur])
          {
             arr[dest] = arr[cur];
             cur--;
             dest--;
          }
         else
         {
             int count = 2;
             while(count--)
             {
                arr[dest--] = 0;
             }
             cur--;
         }
      }
    }
}s1;

启示:这两道题都可以用双指针来解决问题,他们都用了一个指针cur来探路,一个指针dest来处理我们需要做的事,如果指针从左往右不行,我们试试从右往左,先试着画图模拟出移动和指针的更新规律。

  • 54
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 55
    评论
C语言是一种广泛应用于系统编程和嵌入式开发的高级编程语言。函数指针是C语言中的一个重要概念,它可以指向函数,并且可以通过函数指针来调用函数。快速排序算法是一种常用的排序算法,它的核心思想是通过分治的策略将一个大问题分解为多个小问题,并通过递归的方式解决这些小问题。 下面是C语言中使用函数指针实现快速排序算法的示例代码: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 快速排序的分区函数 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选取最后一个元素作为基准 int i = (low - 1); // 定义一个指针,用于指向小于基准的元素 for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } // 快速排序函数 void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 将数组分区,并获取分区点的位置 quickSort(arr, low, pi - 1); // 对分区点左边的子数组进行快速排序 quickSort(arr, pi + 1, high); // 对分区点右边的子数组进行快速排序 } } // 打印数组元素 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {10, 7, 8, 9, 1, 5}; int n = sizeof(arr) / sizeof(arr[0]); printf("原始数组:"); printArray(arr, n); quickSort(arr, 0, n - 1); printf("排序后的数组:"); printArray(arr, n); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值