目录
双指针
双指针可分为两种,快慢指针(一前一后),首尾指针(一头一尾),一般来说基本上最多遍历一次即可实现任务。
解法一:两指针从左到右遍历,交换0,1
2 | 0 | 2 | 1 | 1 | 0 |
P0,P1,i |
找到了0,与P0交换,并且P0,P1均需要移动
找到了1,与P1交换,仅仅移动P1,增加1序列的长度
0 | 2 | 2 | 1 | 1 | 0 |
P0,P1 | i | ||||
0 | 1 | 2 | 2 | 1 | 0 |
P0 | P1 | i | |||
0 | 1 | 1 | 2 | 2 | 0 |
P0 | P1 | i | |||
0 0 | 0 0 | 1 1 | 2 修正:1 | 2 2 | 1 修正:2 |
P0 | P1 | 修正:P1 | i |
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;
}