指针系列详解

本文介绍了双指针算法在排序(快速排序颜色问题)、二分查找、滑动窗口(区间和与子串查找)以及中心扩散(回文串检测)中的应用,展示了如何利用该算法解决常见IT技术问题。
摘要由CSDN通过智能技术生成

目录

双指针

二分查找 

滑动窗口

中心扩散(回文串常用)


双指针

双指针可分为两种,快慢指针(一前一后),首尾指针(一头一尾),一般来说基本上最多遍历一次即可实现任务。

 解法一:两指针从左到右遍历,交换0,1

202110
P0,P1,i

找到了0,与P0交换,并且P0,P1均需要移动

找到了1,与P1交换,仅仅移动P1,增加1序列的长度

022110
P0,P1i
012210
P0P1i
011220
P0P1i

0

0

0

0

1

1

2

修正:1

2

2

1

修正:2

P0P1修正:P1i

0元素时一定连续排列的,1断开

当i检验到0元素时

如果是202110,会出现上述1交换出去的情况,当P0指向1时

而P0指向的元素不可能为0,因为0都在P0后面

因此当P0<P1时,交换完了P0元素后,需要再进行不断交换i,P1

如果P0指向1,交换出来的1交换回去

如果P0指向2,无伤大雅

void swap(int *a, int *b) {
    int t = *a;
    *a = *b, *b = t;
}

void sortColors(int *nums, int numsSize) {
    int p0 = 0, p1 = 0;
    for (int i = 0; i < numsSize; ++i) {
        if (nums[i] == 1) {
            swap(&nums[i], &nums[p1]);
            ++p1;
        } else if (nums[i] == 0) {
            swap(&nums[i], &nums[p0]);
            if (p0 < p1) {
                swap(&nums[i], &nums[p1]);
            }
            ++p0;
            ++p1;
        }
    }
}

解法二:两指针分别相向移动,停止标志:i<P2

void swap(int *a, int *b) {
    int t = *a;
    *a = *b, *b = t;
}

void sortColors(int *nums, int numsSize) {
    int p0 = 0, p2 = numsSize - 1;
    for (int i = 0; i <= p2; ++i) {
        while (i <= p2 && nums[i] == 2) {
            swap(&nums[i], &nums[p2]);
            --p2;
        }
        if (nums[i] == 0) {
            swap(&nums[i], &nums[p0]);
            ++p0;
        }
    }
}

二分查找 

左闭右闭

       当情况为左闭右闭的时候,左指针毋庸置疑是从0索引开始,因为是右闭区间,右指针可以取到右端点,所以右指针的起始点应该为数组长度-1,而while中更新的规则,因为右指针可以取到右边界,所以可以设置left<=right为循环条件,也就是查找过程中right可以和left相等,而更新规则为:left=middle+1,right=middle-1,因为right可以取到middle,所以我们设置right的每次更新为right=middle-1;

返回值的选取(以插入位置为例,升序数组)

当left与right重合的时候,下一次必定中止循环

      如果mid指向的元素比目标小,那么插入的位置就是mid前面,right=mid-1,left不变,插入后就在mid,数值上与left相等

如果mid指向的元素比目标大,那么插入的位置就是mid后面,插入后就在mid+1,left=mid+1

所以返回的值是left

左闭右开

        当情况为左闭右开的时候,左指针毋庸置疑是从0索引开始,因为是右开区间,右指针取不到右端点,所以右指针的起始点应该为数组长度,而while中更新的规则,因为右指针取不到右边界,所以可以设置left<right为循环条件,也就是查找过程中right不可以等于left,而更新规则为:left=middle+1,right=middle,因为right可以取不到middle,所以设置middle为右边开区间的部门。

只用一种,推荐为左闭右闭,方便理解

int search(int* nums, int numsSize, int target) {
    if(numsSize==0)
    return -1;
    if(numsSize==1)
    return target==nums[0]?0:-1;
    int l=0,r=numsSize-1,mid;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(target==nums[mid])
        return mid;
        if(nums[0]<=nums[mid])//前半部分有序
        {
            if(target<nums[mid]&&target>=nums[0])
            r=mid-1;
            else
            l=mid+1;
        }
        else//前半部分无序
        {
            if(target>nums[mid]&&target<=nums[numsSize-1])
            l=mid+1;
            else
            r=mid-1;
        }
    }
    return -1;
    
}

滑动窗口

注意需要找到第一个窗口,然后进行滑动,类似于动态区间

1.[left, right]闭区间为窗口区间,left = right = 0

2.Left固定,right递增,[left, right]符合条件(该题是区间和>=target)时,进行3

right固定,left递增,

3.若区间满足更新条件区间和>=target 且区间长度更小,更新,增加left,

若区间和小于target,进行2

对于区间[0,right],可以理解为想要求以nums[right]为结尾的连续子数组和>=target的最小长度,

     若sum([0,right]) < target,那么对任意right结尾的连续子数组[0,right],[1,right],...,[right-1,right],[right]其和sum都<target(因为你数组全是正数),因此我们判断下一个区间[0, right+1].

    若sum([0,right]) >= target,那么移出一个元素,sum仍可能>=target,因此,我们要判断[1,right],[2,right]...,[right,right]的和,必有left使得[left,right]的和<target, [left-1,right]的和>=target,那么此时就是就没有必要再增加left了,我们再判断下一个区间[0,right+1](注意这里左边是0)

      上面可知,我们依次判断的区间是[0,0], [0,1], ..., [0,right], [0, right + 1], [0, right + 2]...,

     滑窗的话,我们计算[0,right+1]时,要使用[0,right]获得的left值(left满足[left-1, right] >= target,[left,right] < target), 使得区间[0,right+1]的计算从[left, right+1]开始,为什么可以这样?因为[left-1, right] >= target,最小长度必须比它小才可以,我们就没有必要逐个计算'[0,right+1], [1,right+1], ...[left-1, right+1]',这些子数组长度太长了,不会更新答案,直接从[left,right+1]开始就可以。

int minSubArrayLen(int target, int* nums, int numsSize) {
    //start and end
    int left=0,right=0,sum=0,count=0;
    while(right<=numsSize-1)
    {
        sum=sum+nums[right];
       if(sum>=target)//找到第一个符合条件的区间
        {
            count=right-left+1;
            //left递增,right不动
            while(left<=right&&right<=numsSize-1)
            {
                if(sum-nums[left]>=target)
                {
                    sum=sum-nums[left];
                    left++;
                    count=count<(right-left+1)?count:(right-left+1);
                }
                else
                {
                    right++;
                    if(right<=numsSize-1)
                    sum=sum+nums[right];
                }
            }
        }
        right++;
    }
    return count;
}

怎么判断区间内是否存在重复的字母?我们可以借助哈希表来检索

right向右递增,如果s[right]重复,那么区间停止增长,同时left递增,相当于区间向右滑动一格,应当注意的是,区间向右滑动时,要把left对应的字母去掉,也即将hash[ s[left] ]=0,这样新区间内就不会检索出先前的字母了。

int lengthOfLongestSubstring(char* s) 
{
    //滑动窗口+哈希表
    int max=1;
    int n=strlen(s);
    if(n==0)
    return 0;
    int hash[256]={0};
    int right=0,left=0;
    while(s[right]!='\0')
    {
        if(hash[s[right]]==0)
        {
            hash[s[right]]++;
            right++;
        }
        else
        {
            hash[s[left]]--;//去掉left对应的字母
            left++;
        }
        //更新数值
        max=max>(right-left)?max:right-left;
    }

    return max;
}

中心扩散(回文串常用)

 

思路:取定一个位置(中心点),向左右进行扩散,如果左右元素相等,即为回文子串。

注意:奇数扩散和偶数扩散中心点不同

如果是“bab",回文子串的中心点是a,如果是”acca",“acccca"这种字符串连续相同的字符串,则它们的中心点是”cc" .

因此奇数长度的中心点为i,那么扩散就是left=i-1,right=i+1。偶数长度的中心点为i+0.5(实际不存在),left=i-1,right=i+1

对于i=0/i=n-1这样的特殊边界,不进入循环单独处理即可,但要保证默认为第一个字母(不存在回文子串)

​
//扩散函数
void help(char *s, int N, int left, int right, int *start, int *len) 
{
    
while (left >= 0 && right < N && s[left] == s[right]) 
//扩散过程,当处于边界时,不进入扩散(i-1<0//i+1=N),此时start初始值为0。
//如不存在回文串,则默认第一个字母。
        left--, right++;
    
if (right - left - 1 > *len) //right-left+1-2(跳出循环增加两个字母)
    {                            // 如果找到更长的子串,更新长度
        *start = left + 1;
        *len = right - left - 1;
    }
}

char * longestPalindrome(char * s)
{
int N=strlen(s), start = 0, len = 0; // N 字符串长度,start 子串起始位置,len 子串长度
    
for (int i = 0; i < N; i++)     // 奇数长度的回文子串
   help(s, N, i-1, i+1, &start, &len);

for (int i = 0; i < N; i++)     // 偶数长度的回文子串
     help(s, N, i, i+1, &start, &len);
    
s[start + len] = '\0';          // 原地修改返回
    return s + start;
}

​

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值