双指针分为三种:
(1)普通指针:多是两个指着往同一个方向移动(无快慢之分)
(2)对撞指针:两个指针,一个位于头,一个位于尾,它们向中间移动(主要应用于有序数组)
(3)快慢指针:两个指针,起始点位于开头,当A条件成立时,A指针++;
同理:当B条件成立时,B指针++(其中A,B的条件束缚不同因此产生快慢之分)
我们这里来介绍第二种(2)对撞指针:
第一个例子:找相应数字求和==一个特值:
题目:给定一个有序数组(数组是递增的),如数组arr = {1,4,5,7,9};找两个数之和为12,找到一组即可停止。
方法一:暴力遍历:两层for循环查找即可,但时间复杂度就要为O(N^2)了
void find(int *a,int n,int temp)
{
for(int i=0;i<n;i++)
{
for(int j=i;j<n;j++)
{
if(a[i]+a[j]==temp)
{
printf("%d+%d=%d\n",a[i],a[j],temp);
}
}
}
}
你发现了吗?:因为是有序的,所以顺序查找会的值会逐渐增加,但效率太低了
这时我们就可以采用方法二:对撞指针来更高效的完成这个任务
对撞指针的思路类似于二分查找:将i指针放在首,将j指针放在尾
则会有以下三种情况:
(1):a[i]+a[j]<temp,我们则可知这两个指针对应的数组元素的和小于特值,但此时a[j]已经是数组里最大的元素了,所以我们需要动i指针,使它++,对应下个更大的a[i]元素
(2):a[i]+a[j]>temp,同理,a[i]已经是数组里最小的元素了,你们两的和还要比temp大,就只能让j--,从右向左寻找依次寻找更小的数,所以j--,对应下个更小的a[j]元素
(3)a[i]+a[j]==temp,不多说了,满足条件,跳出循环即可
主要思路有了:但由于不太可能判断一次就出结果,所以我们要在外面加一个循环,循环跳出的条件是什么呢?:很简单:当左指针>右指针时(注意是下标,不是下标对应的数组元素),说明都搜了个遍了,都没有结果,那就证明数组里真的没有满足的这两个元素
void find(int *a,int n,int temp,int i,int j)
{
//i为首指针,j为尾指针
while(i<j)
{
if(a[i]+a[j]<temp)
{
i++;
}
else if(a[i]+a[j]>temp)
{
j--;
}
else
{
printf("%d+%d=%d\n",a[i],a[j],temp);
break;
}
}
}
时间复杂度就被我们优化到了O(N) ^-^
第二个例子:判断回文串:
输入字符串,判断其是否为回文串 若是:输出Y 否则:输出N
回文串:回文串就是从左边读和从右边读的结果是一样的,例如:aba qweewq 121 111
思路解析:初始化flag=1,将i指针指向首,将j指针指向尾,如果不是回文串我们需要flag=0做标记
(1):如果a[i]==a[j],则i指针++,j指针--
(2):如果a[i]!=a[j],flag=0,break跳出循环,这个字符串不是回文串
循环条件为:左指针i<右指针j
(1)if(flag==1):它没有进入过a[i]!=a[j] :则可知该字符串为回文串
(2)else: 它进入了a[i]!=a[j]:则可知该字符串不是回文串
代码呈上:
#include<stdio.h>
#include<string.h>
int main()
{
char str[255];
while(gets(str))
{
int len=strlen(str);
int i=0;
int j=len-1;
int flag=0;
while(i<j)
{
if(str[i]==str[j])
{
i++;
j--;
}
else
{
flag=1;
break;
}
}
if(flag==0)
{
printf("Y\n");
}
else
{
printf("N\n");
}
}
return 0;
}
第三个例子:反转字符串
题目本身不难,唯一的难点是它的要求:不能再去开一个数组
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
void Swap(int *left,int *right)
{
char temp=*right;
*right=*left;
*left=temp;
}
void ReverseString(char* s, int size){
char* left = s;//指向起始地址:左指针
char* right = s + size - 1;//指向末位地址:右指针
while(left < right)//左指针小于右指针的下标
{
Swap(&left,&right);
left++;
right--;
}
第四个例子:选择排序:
本质:应用双指针,begin指针指向首,end指针指向尾
使用for循环在begin——end范围内寻找并记录最大的数下标:maxi,和最小数字的下标:mini
这里我们想将数字进行升序排列,只需将begin——end范围内的最小数下标与begin交换即可
即:Swap(&a[begin],&a[mini]) 同理将最大数下标与end交换: Swap(&a[end],&a[maxi])
之后让begin++,end--即可;循环条件还是左指针小于右指针
#include<stdio.h>
void Swap(int* p1, int* p2)
{
int tmp = *p2;
*p2 = *p1;
*p1 = tmp;
}
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while (begin < end)
{
int mini = begin;
int maxi = begin;
for (int i = begin; i <= end; i++)
{
//寻找最小的数字的下标
if (a[i] < a[mini])
{
mini = i;
}
//寻找最大数字的下标
if (a[i] > a[maxi])
{
maxi = i;
}
}
Swap(&a[begin],&a[mini]);//最小的放到第一位
if (begin == maxi)
{
maxi = mini;
}
Swap(&a[end],&a[maxi]);//最大的放到最后一位
++begin;
--end;
}
}
void Printarray(int* a, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
}
void TestSelectSort()
{
int a[] = { 3,5,4,2,1 };
int n = sizeof(a) / sizeof(a[0]);
SelectSort(a, n);
Printarray(a, n);
}
int main()
{
TestSelectSort();
return 0;
}
请注意:为什么我又加了个条件判断(如果:begin==maxi):即如果最大数的下标位于第一个的话
我们将最小值的下标赋值给最大值的下标?为什么?
因为我们是先进行的最小值与begin的交换,交换后,如果很不巧,begin对应的数字就是最大数,那么原来maxi的值会被mini对应元素的值覆盖。
举个例子:a[]={5,3,1,2,4};
如果不加这个判断条件:
第一次mini对应值与begin对应值交换的结果:a[]={1,3,5,2,4};
第一次maxi对应值与end对应值的交换的结果:a[]={4,3,5,2,1};
产生错误的结果是因为maxi被第一次mini和begin交换时被破坏了
正确的情况是:
mini与begin 的对应值:a[]={1,3,5,2,4};
maxi与end 的对应值:a[]={1,3,4,2,5}:
---------------------
作者:lihua777
来源:CSDN
原文:https://blog.csdn.net/lihua777/article/details/122243651
版权声明:本文为作者原创文章,转载请附上博文链接!