在牛客网和leetcode等网站刷题的过程中,时常会遇到一些使用双指针和三指针解决问题的实例。今天,我来介绍这两种方法,相信你会对指针的应用会提高一个档次。



目录

  • 移除元素
  • 删除有序数组中的重复项
  • 合并两个有序数组


在下面的讲解的过程中,我会以leetcode里的题目为例子,采用双指针和三指针方法进行实现,如果有其他实现方法,我也会写出来,与双指针和三指针进行比较。

注意,leetcode上是按照的核心代码的形式进行答题的,但是在下面的讲解的过程中,我主要以在vs2010编辑器书写所有的代码的形式进行讲解,leetcode里的题目只是作为引入。



移除元素


题目链接

在这里,题目的要求是返回删除元素后数组的元素新长度,我修改一下,直接打印出修改完的数组的元素

假设,数组元素为0,1,2,2,3,0,4,2,我要删除元素大小为2的所有元素。

不使用指针的方法

首先选择采用不使用指针的方法。

我定义变量i,让i从0开始,增大到为数组长度减一,让arr[i]遍历一遍数组。

如果在期间寻找到与要删除的数字相同的元素时,如:

arr[i]找到元素时,此时的arr[i]等于要删除的数字,那么我就让后面的数字依次往前替换,同时,数组的长度减一。










最后数组的长度减一。(因为最后一个元素也已经被拷贝到前面去了)。

观察可以发现,刚才的2的元素已经被覆盖掉了,但是由于向前覆盖,arr[i]现在的位置又出现了一个2,这里就有一个小技巧,当arr[i]是要删除的数字时,覆盖以后,i不进行增加,让程序再次检查arr[i]的位置。

代码的实现如下:

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr[] = {0,1,2,2,3,0,4,2};            //需要进行删除的数组int val = 2;                              //需要删除的元素大小int i = 0;int j = 0;int len = sizeof(arr)/sizeof(arr[0]);while(i < len)                           //下标要小于数组的元素个数{if(arr[i] == val)                    //数组元素等于要删除的元素大小{j = i;                           //利用j值进行移动,防止i值的改变while(j < len-1){arr[j] = arr[j+1];           //往前替代j++;}len--;                           //数组的长度减一}else{i++;                             //数组元素不等于要删除的元素大小,i值加一,arr[i]向后寻找}}print(arr,len);return 0;
}

运行结果如下:

由运行结果可知,该代码满足了我们的要求,但是代码满足要求就足够了吗?

这串代码的时间复杂度是:O(N^2)
空间复杂度是:O(1)

接下来,我来使用另外一种方法。

采用创建新数组和使用双指针的方法

如图,我创建一个新数组。

接下来,让指针src和指针dest分别指向原数组和新数组。

如果指针src指向的元素不是2时,将指针src指向的内容赋值到指针dest指向的空间。


代码的实现如下:

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr[] = {0,1,2,2,3,0,4,2};            //需要进行删除的数组int arr1[20] = {0};int val = 2;                              //需要删除的元素大小int* src = arr;                     int* dest = arr1;int i = 0;int j = 0;int len = sizeof(arr)/sizeof(arr[0]);while(i < len){if(*(src + i) != val)                //指针(src+i)不等于val时,赋值到指针(dest+i)指向的空间,i++,j++{*(dest + j) = *(src + i);i++;j++;}else                                //指针(src+i)等于val时,i++,找到下一个元素{i++;}}print(arr1,j);                         //打印arr1数组中的j个元素return 0;
}

运行结果如下:

由该运行结果可以得知,该代码依然可以满足我们的要求,现在,我来研究该代码的效率。

这串代码的时间复杂度是O(N)
空间复杂度是O(N)

显然,这一串代码是空间换取时间的典型例子,但是双指针的优点还是没有完全发挥出来,接下来,我来实现另外的一种代码,完全发挥出双指针的优点。

使用双指针
我定义两个指针,分别是指针src和指针dest,开始的时候,这两个指针都指向了数组的第一个元素。

接下来,我需要遍历一次数组,如果指针src指向的内容等于要删除数字的值的话,指针src向后走一步,如果指针src指向的内容不等于要删除数字的值的话,那么将指针src指向的内容赋值到指针dest指向的内容,并且两个指针向后走一步。

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr[] = {0,1,2,2,3,0,4,2};            //需要进行删除的数组int arr1[20] = {0};int val = 2;                              //需要删除的元素大小int* src = arr;                     int* dest = arr1;int i = 0;int j = 0;int len = sizeof(arr)/sizeof(arr[0]);while(i < len){if(*(src + i) != val)                //指针(src+i)不等于val时,赋值到指针(dest+i)指向的空间,i++,j++{*(dest + j) = *(src + i);i++;j++;}else                                //指针(src+i)等于val时,i++,找到下一个元素{i++;}}print(arr1,j);                         //打印arr1数组中的j个元素return 0;
}

运行结果如下:

由运行结果可以得知,该串代码符合我们的要求。

这串代码的时间复杂度:O(N)
空间复杂度:O(N)



删除有序数组中的重复项


题目链接

假设在数组元素为0,0,1,1,1,2,2,3,3,4,我要删除一些重复的数组,并且只留下其中一个。

去重算法
使用两个指针src和dest,开始时都指向数组的第一个元素,如果两个指针src和dest的内容相等的话,那么指针src向后走一步,如果两个指针src和dest的内容不相等的话,就将指针src指向的内容赋值到指针dest指向的后面一步的空间。

代码的实现如下:

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr[] = {0,0,1,1,1,2,2,3,3,4};int* src = arr;                         //指针src存储数组首元素的地址int* dest = arr;                        //指针dest存储数组首元素的地址int i = 0;int j = 0;int len = sizeof(arr)/sizeof(arr[0]);while(i < len)                          //i要小于数组长度,不用判断j,因为i肯定会先达到大于数组长度的条件{if(*(dest + j) == *(src + i))       //如果两个指针指向的内容相等,src指针要向后走一步,i++可以实现该效果{i++;}else                                //如果两个指针指向的内容不相等,dest指针向后走一步。src指针指向的内容赋值到指针dest指向的内容,然后i++{j++;*(dest + j) = *(src + i);i++;}}print(arr,j + 1);return 0;
}

运行结果如下:

由运行结果可以得知,该代码符合我们的要求。

观察可以发现,去重算法依然是以双指针为基础,进行操作的,可见双指针运用的广泛。

这串代码的时间复杂度是:O(N)
空间复杂度是:O(1)



合并两个有序数组

在这道题中,有两个版本,一个较为简单,一个较为难。leetcode上这一道题是难的版本,而我要先讲解简单版本。

题目要求:给你两个按递增顺序排列的整数数组nums1 和nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 和 nums1 ,使合并后的数组同样按递增进行排列。

假设nums1的数组元素是1,2,3,5,6,9,nums2的元素是2,5,6。

我依然采用双指针的方法,先创建一个新数组和两个指针,第一个指针指向nums1的第一个元素,第二个指针指向nums2的第一个元素,比较这两个指针指向的内容谁大谁小,小的就赋值到新数组。

然后继续遍历,直到数组nums1和数组nums2的元素都已经被赋值到新数组中。

代码的实现如下:

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr1[] = {1,2,3,5,6,9};int arr2[] = {2,5,6};int arr[20] = {0};                              //创建新数组int* src1 = arr1;                               //创建一个指针指向数组arr1的首元素地址int* src2 = arr2;                               //创建一个指针指向数组arr2的首元素地址int i = 0;int j = 0;int k = 0;int len1 = sizeof(arr1)/sizeof(arr1[0]);int len2 = sizeof(arr2)/sizeof(arr2[0]);while(i < len1 && j < len2)                     //i值和j值分别小于数组对应长度,就进入循环    {if(*(src1 + i) < *(src2 + j))               //*(src1 + i)指向的元素较小,将*(src1 + i)赋值到新数组中{arr[k] = *(src1 + i);k++;                                    //k++,保证赋值到新数组的下一个位置i++;                                    //i++,保证被复杂过的元素不再被复制}else{arr[k] = *(src2 + j);                   //*(src2 + j)指向的元素较小,或者两个指针指向的元素相等(复制哪个指针的内容都可以),将*(src2 + j)的元素赋值到新数组k++;                                    //k++,保证赋值到新数组的下一个位置j++;                                    //j++,保证被复制过的元素不再被复制}}if(j >= len2)                                   //当*(src2 + j)走完arr2数组,而*(src1 + i)还没有走完arr1数组的情况{while(i < len1){arr[k] = *(src1 + i);k++;i++;}}if(i >= len1)                                    //当*(src1 + i)走完arr2数组,而*(src2 + j)还没有走完arr2数组的情况{while(j < len2){arr[k] = *(src2 + j);k++;j++;}}print(arr,k);return 0;
}

运行结果如下:

由运行结果可以得知,此串代码符合我们的要求。

这串代码的时间复杂度是O(N)
空间复杂度是O(N)

接下来,我来实现较难的版本,注意在这一道题中,两个版本的题目是不一样的,所以时间复杂度和空间复杂度不做比较。

题目要求:给你两个按递增顺序排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按递增顺序排列。

题目链接

假设第一个数组的元素是:1,2,3,0,0,0,第二个数组的元素是2,5,6,第一个数组后面留三个是给第二个数组的元素留的位置,所以第一个数组只看前三个元素,是递增顺序的。

在这一版本中,不再要求我们合并到一个新的数组,而是要合并到第一个数组中,并且还要按照递增的顺序进行排列。

那么,在这一版本中,就要引入三指针的方法。

先定义三个指针分别是src1、src2、dest,指针src1指向第一个数组递增顺序中的最后一个元素(也就是3)。指针src2指向第二个数组的最后一个元素,也就是6,指针dest指针指向第一个数组最后一个元素(包括非顺序的),如下:


接下来,比较指针src1和指针src2指向的内容,哪个指针指向的内容大时,就赋值到指针dest指向的空间处,并且该指针和指针dest向前走一步。

代码的实现如下:

#include<stdio.h>
void print(int arr[],int len)               //打印函数
{int i = 0;for(i = 0; i < len; i++){printf("%d ",arr[i]);}printf("\n");
}
int main()
{int arr1[] = {1,2,3,0,0,0};int arr2[] = {2,5,6};int len1 = sizeof(arr1)/sizeof(arr1[0]);int len2 = sizeof(arr2)/sizeof(arr2[0]);int* str1 = arr1;      int* str2 = arr2;             int* dest = arr1;             int i1 = len1 - len2 - 1;     //让arr1加该值,len1的值为6,len2的值为3,len1减len2得到3,3再减一得到2,arr是数组首元素地址,arr+2找到3的位置int i2 = len2 - 1;            //让arr2加该值,找到数组arr2的最后一个元素int j = len1 - 1;             //让arr1加该值,找到数组arr1的最后一个元素while(i1 >= 0 && i2 >= 0)     //i1和i2的值要大于0,防止越界{if(*(str1 + i1) > *(str2 + i2))         //*(str1 + i1)大于*(str2 + i2)时,将*(str1 + i1)的值赋值到*(dest + j){*(dest + j) = *(str1 + i1);i1--;                               //i1减减,找到数组arr1的上一个元素j--;                                //j减减,继续覆盖元素}else{*(dest + j) = *(str2 + i2);        //*(str2 + i2)大于*(str1 + i1)时,或者*(str2 + i2)等于*(str1 + i1)时(复制哪个值都可以),将*(str2 + i2)的值赋值到*(dest + j)i2--;                              //i1减减,找到数组arr2的上一个元素j--;                               //j减减,继续覆盖元素}}while(i2 >= 0)                             //i1没有遍历完没关系,因为就是要覆盖到数组arr1,要保证i2遍历完{*(dest + j) = *(str2 + i2);i2--;j--;}print(arr1,len1);return 0;
}

运行结果如下:

由运行结果可以得知,此串代码符合我们的要求。

这串代码的时间复杂度:O(N)
空间复杂度:O(1)

今天,对于指针讲解就到处结束了,指针是非常重要的东西,特别是后面数据结构的学习中,指针更是频繁使用,所以对于指针掌握要求应当更高。

关注点一点,下期更精彩。

指针的进阶应用之双指针、三指针相关推荐

  1. [C语言简明教程] 指针的进阶(上)

    文章目录 前言 一.指针是什么? 二.指针的进阶 1. 字符指针 2.指针数组 3.数组指针 3.1数组指针定义 3.2 &数组名 和 数组名 3.3 数组指针的使用 4. 数组参数.指针参数 ...

  2. c语言野指针导致问题,C语言进阶之路(三)----野指针的产生原因及解决办法

    1.会产生野指针的做法 #include //这就是一种错误的写法 int main(){ int *p = NULL; p = (int *)malloc(); //释放P所指向的内存空间,但指针变 ...

  3. [C语言简明教程] 指针的进阶(下)

    文章目录 前言 一.函数指针 二.函数指针数组 三.回调函数 总结 前言 C语言中指针的重要性是:通过指针不仅可以对数据本身,还可以对存储数据的变量地址进行操作.指针能够帮助我们快速地传递数据,减少内 ...

  4. 75. 颜色分类(三指针、Python)

    学习三指针的思想及应用 题目描述 文章目录 方法一:双指针(两遍循环) 方法二:三指针(一遍循环) 方法三:三指针(交换0和2) 方法一:双指针(两遍循环) 我们可以考虑对数组进行两次遍历.在第一次遍 ...

  5. 【C 语言】二级指针内存模型 ( 指针数组 | 二维数组 | 自定义二级指针 | 将 一、二 模型数据拷贝到 三 模型中 并 排序 )

    文章目录 一.指针数组 和 二维数组 数据 拷贝到 自定义二级指针 中 1.函数形参 设计规则 2.三种内存模型 对应 函数形参 指针退化规则 二.完整代码示例 一.指针数组 和 二维数组 数据 拷贝 ...

  6. C语言程序设计 | 指针的进阶(一):字符指针、数组指针、指针数组、函数指针

    指针的进阶(一)目录: 字符指针 数组指针和指针数组 函数指针 字符指针 在开始讲解这一章节之前,我们需要了解指针前面声明的类型的意义 类型 * 指针名 对于指针来说,我们在给指针进行声明时,我们声明 ...

  7. C++中各种智能指针的实现及弊端(三)

    C++中各种智能指针的实现及弊端(三) 文章目录 C++中各种智能指针的实现及弊端(三) 一: std::unique_ptr 二.**std::unique_ptr的缺陷** 一: std::uni ...

  8. [Leetcode][第24题][JAVA][两两交还的链表中的节点][递归][三指针]

    [问题描述][中等] [解答思路] 1. 递归 时间复杂度:O(N) 空间复杂度:O(N) class Solution {public ListNode swapPairs(ListNode hea ...

  9. 带你刷笔试关的小怪|详解指针习题和面试题【C语言/指针/进阶】

    文章目录 前言 9. 指针和数组笔试题解析 复习回顾 一维数组 字符数组 二维数组 10. 指针笔试题 笔试题1: 笔试题2 笔试题3 笔试题4 笔试题5 笔试题6 笔试题7 (#)笔试题8 结语 前 ...

最新文章

  1. php 根据位置显示地图,php通过地址获得百度地图经纬度(地理编码)
  2. WinHEC 2008 走马观花 [多图杀猫]
  3. 直播预告 | 小米人工智能部崔世起:小爱同学全双工技术实践
  4. SAP Analytics Cloud Model的delta upload(增量导入)功能
  5. Activity后台运行一段时间回来crash问题的分析与解决
  6. python如何使数据加行_如何使用 Python 插入行
  7. 自适应共振理论网络 ART
  8. HackerRank Lists
  9. 很火的娇喘整蛊源码(带演示站)
  10. mmd动作:Bad End Night
  11. mac下如何设置excel下拉表格
  12. 离线打包报错缺少io.dcloud.PandoraEntry
  13. 在夜神模拟器内部安装App
  14. java 音频 网络传输_如何流式传输音频?
  15. 多媒体教学计算机遥控,多媒体教学系统使用说明
  16. LWR 局部加权线性回归算法
  17. matlab有用小工具
  18. 新的放假规定,大年三十还得朝九晚五!
  19. 助眠的产品有哪些?失眠值得拥有和了解的助眠好物以及方法
  20. auto盘病毒清除器.bat

热门文章

  1. 网络黑客攻防学习平台之选择题
  2. gnome linux 重装_如何安装 gnome 3
  3. Git远程分支覆盖本地分支的详细介绍
  4. 新手玩荔枝派 f1c100s nano折腾笔记(二)
  5. Mysql数据库表如何设计?
  6. ie设置默认,打开快捷方式会弹出2个浏览器网页
  7. css做个波浪悬浮球
  8. 腾讯前端面试题、面经
  9. 解决Android模拟器卡慢的问题
  10. Mongodb数据库转换为表格文件的库