leetcode【数据结构简介】《数组和字符串》卡片——双指针技巧

双指针技巧

双指针技巧一:

有时候,我们可能需要同时使用两个指针来进行迭代。

从一个经典问题开始: 反转数组中的元素。

其思想是将第一个元素与末尾进行交换,再向前移动到下一个元素,并不断地交换,直到它到达中间位置。

我们可以同时使用两个指针来完成迭代:一个从第一个元素开始,另一个从最后一个元素开始。持续交换它们所指向的元素,直到这两个指针相遇。

总结

使用双指针技巧的典型场景之一是你想要

从两端向中间迭代数组。

这时你可以使用双指针技巧:

一个指针从始端开始,而另一个指针从末端开始。

值得注意的是,这种技巧经常在排序数组中使用。

相关程序练习

1. Reverse String(反转字符串)

  • 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
  • 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
  • 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

Input: [“h”,“e”,“l”,“l”,“o”]
Output: [“o”,“l”,“l”,“e”,“h”]

实现代码

void reverseString(char* s, int sSize){    
    int front=0, back=sSize-1;    
    while(front<back){        
        char temp;        
        temp = s[front];        
        s[front] = s[back];        
        s[back] = temp;        
        front++;back--;    
    }    
    return s;
}

执行结果

反转字符串执行结果

2. 数组拆分 Array Partition I

给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大。

输入: [1,4,3,2]
输出: 4
解释: n 等于 2, 最大总和为 4 = min(1, 2) + min(3, 4).

算法:冒泡算法

比较简单就不赘述了

此算法超时

算法:鸡尾酒算法plus

鸡尾酒算法算是冒泡算法优化的优化->顶级冒泡算法~

int arrayPairSum(int* nums, int numsSize){    
    bool isSorted = true;    
    int lastLeftIndex=0, lastRightIndex=0;    
    int leftBorder = 0, rightBorder = numsSize - 1;
    for(int i=0; i<numsSize/2; i++){
        for (int j = leftBorder; j < rightBorder; j++) {            
            if (nums[j] > nums[j + 1]) {                
                int temp = nums[j];                
                nums[j] = nums[j + 1];                
                nums[j + 1] = temp;                
                lastRightIndex = j;                
                isSorted = false;            
            }        
        }
        rightBorder = lastRightIndex;        
        if (isSorted) break;
        
        for (int j = rightBorder; j > leftBorder; j--) {            
            if (nums[j] < nums[j - 1]) {                
                int temp = nums[j];                
                nums[j] = nums[j - 1];                
                nums[j - 1] = temp;                
                lastLeftIndex = j;                
                isSorted = false;            
            }        
        }        
        leftBorder = lastLeftIndex;        
        if (isSorted)  break;
    }
    for(int i=0; i<numsSize; i++) printf("%d  ",nums[i]); printf("\n");
    int sum=0;    
    for(int index=0; index<numsSize; index++){        
        if(!(index%2)) sum += nums[index];    
    }
    return sum;
}    

很不幸,此算法也超时

算法:使用qsort函数

int comp(const void* a, const void* b){    
    return (*(int*)a > *(int*)b);
}
int arrayPairSum(int* nums, int numsSize){    
    qsort(nums, numsSize, sizeof(int), comp);    
    int sum = 0;    
    for(int i=0;i<numsSize;i+=2){        
        sum += nums[i];    
    }    
    return sum;
}

万幸,这个代码没有超时 and 关于qsort()

数组拆分执行结果

3. 两数之和II-输入有序数组

给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。

输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

法一:暴力解决

忽略了题中已经排好序的特点,不可取

int* twoSum(int* numbers, int numbersSize, int target, int* returnSize){       
    for(int i=0; i<numbersSize-1; i++){        
        for(int j=i+1; j<numbersSize; j++){            
            if(numbers[i] + numbers[j] == target){            
                * returnSize = 2;                
                * int* two = (int *)malloc(sizeof(int) * 2);                
                two[0] = i+1; two[1] = j+1;                
                return two;            
            }        
        }    
    }    
    *returnSize = 0;    
    return NULL;
}

简单粗暴,但是超出时间限制

法二:双指针

利用排好序的性质:一个从前往后,一个从后往前。

int* twoSum(int* numbers, int numbersSize, int target, int* returnSize){    
    int low=0, high=numbersSize-1;    
    int* two = (int *)malloc(sizeof(int) * 2);    
    while(low<high){        
        int sum = numbers[low] + numbers[high];        
        if(sum==target){            
            two[0] = low + 1;            
            two[1] = high + 1;            
            * returnSize = 2;            
            return two;        
        }        
        else if(sum<target) low++;        
        else high--;    
    }    
    *returnSize = 0;    
    return NULL;
}

两数之和II 执行结果

双指针技巧二:

有时,我们可以使用两个不同步的指针来解决问题。

给定一个数组和一个值,原地删除该值的所有实例并返回新的长度。

如果我们没有空间复杂度上的限制,那就更容易了。我们可以初始化一个新的数组来存储答案。如果元素不等于给定的目标值,则迭代原始数组并将元素添加到新的数组中。
实际上,它相当于使用了两个指针,一个用于原始数组的迭代,另一个总是指向新数组的最后一个位置。

现在让我们重新考虑空间受到限制的情况。
我们可以采用类似的策略,我们继续使用两个指针:一个仍然用于迭代,而第二个指针总是指向下一次添加的位置

总结

这是你需要使用双指针技巧的一种非常常见的情况:

同时有一个慢指针和一个快指针。

解决这类问题的关键是

确定两个指针的移动策略。

与前一个场景类似,你有时可能需要在使用双指针技巧之前对数组进行排序,也可能需要运用贪心想法来决定你的运动策略。【贪心思想相关1】【贪心思想相关2】

相关程序练习

1. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。

法一:交换

从前面开始依次将与val等值的元素 和 从后面开始与val不等值的元素进行对换,最后,前面一部分都是与val不等值的,后面一部分都是与val等值的。

int removeElement(int* nums, int numsSize, int val){    
    int low=0, high=numsSize-1, size=numsSize;    
    while(low<high){        
        if(nums[low]!=val) low++;        
        else if(nums[low]==val){            
            while(nums[high]==val){                
                high--;                
                size--;                
                if(high==low) return low;            
            }            
            int temp = nums[low];            
            nums[low] = nums[high];            
            nums[high] = temp;            
            size--;            
            low++;            
            high--;        
        }
    }    
    if(high==low && nums[low]==val) size--;    
    return size;
}

在这里插入图片描述

法二:就题论题,因题制宜

仔细想想,交换又有什么用呢,后面的元素并不输出。对于此题来说,最适合的代码应该如下:

int removeElement(int* nums, int numsSize, int val){    
    int k=0;    
    for (int i=0;i<numsSize;i++){        
        if (nums[i]!=val){            
            nums[k] = nums[i];            
            k++;        
        }    
    }    
    return k;
}

在这里插入图片描述

2. 最大连续1的个数

给定一个二进制数组, 计算其中最大连续1的个数。

输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.

比较简单就直接附上代码了

int findMaxConsecutiveOnes(int* nums, int numsSize){    
    int max=0;    
    for(int i=0; i<numsSize; i++){        
        int n=0;        
        while(i+n<numsSize && nums[i+n]) n++;        
        i += n;        
        if(n) max = max>n?max:n;    
    }    
    return max;
}

在这里插入图片描述

3. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

法一:暴力解决(也不是很暴力那种)

暴力解决,思路简单,代码简单,运行太久

int minSubArrayLen(int s, int* nums, int numsSize){
    if(numsSize < 1) return 0;    
    int min = numsSize;    
    bool flag=false;    
    for(int i=0; i<numsSize; i++){        
        int sum=0, n=0;        
        while(i+n<numsSize && sum<s) sum+=nums[i+(n++)];        
        if(sum>s-1){            
            flag=true;            
            min = min<n?min:n;        
        }    
    }    
    if(flag) return min;    
    else return 0;
}

在这里插入图片描述

法二:双指针(窗口滑动)

代码:

# define MIN(a,b) a<b?a:b

int minSubArrayLen(int s, int* nums, int numsSize){     
    if(numsSize < 1) return 0;
    int min = INT_MAX , left = 0 , sum = 0;
    for(int i = 0 ; i < numsSize ; i++){        
        sum += nums[i];        
        while(s <= sum){            
            min = MIN(min , (i  - left + 1));            
            sum -= nums[left++];        
        }    
    }    
    return min == INT_MAX ? 0 : min;
}

在这里插入图片描述

小技巧

函数的另一种定义方式

# define MIN(a,b) a<b?a:b

相比较于下面这种更加简洁。

int min(int a, int b){
    return a<b?a:b;
}
发布了13 篇原创文章 · 获赞 2 · 访问量 276

猜你喜欢

转载自blog.csdn.net/AuthurWhywat/article/details/105069649