例题一
一、题目
两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers 按 非递减顺序 排列
-1000 <= target <= 1000
仅存在一个有效答案作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x9i1x6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二、代码
分析:
找nums[i] + nums[j] = target,找到对应的i和j。
由于nums数组是非递减的,也就是nums[i+1] >= nums[i],那么target = a(小一点) + b(大一点)的两个数相加,自然我们想到双指针操作。
思路:
1.设置两个指针:i,j分别指向数组的首尾
2.i,j相向移动,同时判断 nums[i] + nums[j] ==target
2.1 if sum > target , 则说明需要小一点的数,又因为右边的数较大,则j--
2.2 if sum < target ,则说明需要大一点的数,则 i++
2.3 if sum == target ,则记录下i,j 的值,同时 i++, j--
3. 知道i == j时,结束
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* twoSum(int* numbers, int numbersSize, int target, int* returnSize){
// 思路:双指针
// 1.两个指针i,j分别指向数组首尾,
// 2.相向移动,判断numbers[i] + numbers[j] == target
// 2.1如果>target,则需要小一点的元素,则i不动,j--
// 2.2如果<target,则需要大一点的元素,则j不同,i++
// 2.3如果=target,则记录下i,j的索引,然后i++,j--
// 3.直到i==j,则结束
int i = 0;//指向第一个元素
int j = numbersSize - 1;//指向最后一个元素
int *index = (int *)malloc(sizeof(int) * 2);//满足target的元素序号数组
*returnSize = 0;
while(i < j){
if(numbers[i] + numbers[j] > target){
j--;
}else if(numbers[i] + numbers[j] < target){
i++;
}else{
index[(*returnSize)++] = i + 1;
index[(*returnSize)++] = j + 1;
//由于只有一组,所以当=target时,即可退出遍历
break;
// i++;
// j--;
}
}
return index;
}
注意:本题中两数之和要等于target,在移动指针的过程中,保持一个原则就是一个数小 ,另一个数大 。也正是数组是非递减的,才可以用这样——双指针的方式解决问题。
如果数组是非递减的,则使用二重循环的方式找出答案。
时间复杂度:O(n)
空间复杂度:O(1)
例题二
一、题目
验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。字母和数字都属于字母数字字符。
给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
示例 1:
输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。
示例 2:输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。
示例 3:输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
提示:
1 <= s.length <= 2 * 105
s 仅由可打印的 ASCII 字符组成作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x9tqjc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二、代码
分析:
同样采用双指针的思路
思路:
1.设置i,j两个指针,分别指向字符串的首尾
2.i,j相向移动,当遇到非字母时则忽略跳过
3.判断 s[i] == s[j],相等则同时 i++, j-- ;若不相等则直接返回结果false
4.当i==j时,移动结束,可以返回true
bool isCharacterOrNumber(char c){
if(c >= 97 && c <= 122 || c >= 65 && c <= 90 || c >= '0' && c <= '9'){
return true;
}
return false;
}
bool isPalindrome(char * s){
// 对撞指针
// 思路:1. i,j两个指针相向而动
// 2. 若s[i],s[j] 不是字母或者数字,则跳过,i++,或者j--
// 3. 若s[i],s[j] 是大写字母,则转换成小写字母
// 4. 判断 s[i] == s[j] 是则i++,j--;不是则返回false
// 5.直到i == j时,则返回true;
int i = 0;//i指向第一个字符
int j = strlen(s) - 1;//j指向最后一个字符
while(i < j){
//当前字符若不是字母或者数字,则下移
while(!isCharacterOrNumber(s[i]) && i < j){
i++;
}
while(!isCharacterOrNumber(s[j]) && i < j){
j--;
}
//判断i,j指向的元素是否一致
if(tolower(s[i]) != tolower(s[j])){
return false;
}
i++;
j--;
}
return true;
}
时间复杂度:O(n)
空间复杂度:O(1)
例题三
一、题目
反转字符串中的元音字母
给你一个字符串 s ,仅反转字符串中的所有元音字母,并返回结果字符串。元音字母包括 'a'、'e'、'i'、'o'、'u',且可能以大小写两种形式出现不止一次。
示例 1:
输入:s = "hello"
输出:"holle"
示例 2:输入:s = "leetcode"
输出:"leotcede"
提示:
1 <= s.length <= 3 * 105
s 由 可打印的 ASCII 字符组成作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x93lce/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二、代码
思路:对撞指针
1.i,j分别指向字符串的首尾
2.i++,j--当i遇到一个元音字母时,停下;当j遇到一个元音字母时也停下
3.交换s[i] 和 s[j]
4.重复2-3,直到i== j时结束
bool isVowel(char c){
if(tolower(c) == 'a' || tolower(c) == 'e' || tolower(c) == 'i' || tolower(c) == 'o' || tolower(c) == 'u'){
return true;
}
return false;
}
void swap(int i,int j,char *s){
char c = s[i];
s[i] = s[j];
s[j] = c;
}
char * reverseVowels(char * s){
//对撞指针
// 思路:1. i,j分别指向字符串的首尾
// 2. i++,j--,当i遇到一个元音字母时,停下,当j遇到一个元音字母时停下(类似快速排序中每趟确定基准数的过程)
// 3. 交换s[i] 和 s[j]
// 4. 重复2,直至i == j时停止
int i = 0;
int j = strlen(s) - 1;
while(i < j){
while(!isVowel(s[j]) && i < j){
j--;
}
while(!isVowel(s[i]) && i < j){
i++;
}
//交换i,j所指向的元素
swap(i,j,s);
j--;
i++;
}
return s;
}
注意:本题中,i,j指针相向移动的过程有些类似快排算法中确定基准数位置的过程,读者可以体会体会。
时间复杂度:O(n)
空间复杂度:O(1)
例题四
一、题目
盛最多水的容器
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:输入:height = [1,1]
输出:1
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x96n4v/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二、代码
思路1:无脑暴力
二重遍历计算所有的结果,然后比较找到最大值,这里不过多展示。
分析:
本题中,我们不难发现水箱的容量是由最短的木板和两个木板之间的距离决定的,那么,我们是否可以利用这一点使用双指针来解决问题呢?
我们同样采用对撞指针的思路,i,j指向首尾,可以发现,在i,j移动的过程中,木板间距distance在不断减小,如果木板长度不变则,容量一直减小;
而我们目标是要找到最大的容量,那么一定要改变木板长度。
显然如果改变长木板,有可能导致容量没有变化,而更改短木板,则一定会导致容量发生变换。
更改短木板后,更改后的容量再和已有的max容量比较。
思路2: 对撞指针
1.设置i,j两个指针指向首尾
2.每次移动较短木板。
3.记录当前的容量cur和已有最大容量max相比较。若cur>max,则更改max = cur;若cur <max,则不变
4.重复2-3,直至i== j结束。
int maxArea(int* height, int heightSize){
// 思路1:暴力,找出所有水量,找出最大值
// 思路2:双指针,
// 1.水箱的容量由最短的木板决定
// 2.当下一个木板比当前的木板还短或者相等时,则水箱的容量一定小于当前的容量(底变短,高度不变)
// 3.当下一个木板比当前木板长时,则水箱容量可能变大(底变短,高变长)
int i = 0;
int j = heightSize - 1;
//默认第一个木板和最后一个木板组成的水箱最大
int minBord = height[i] < height[j] ? height[i] : height[j];
int max = (j - i) * minBord;
while(i < j){
//移动较短的那个
if(height[i] < height[j]){
i++;
}else{
j--;
}
minBord = height[i] < height[j] ? height[i] : height[j];
max = (j - i) * minBord > max ? (j - i) * minBord : max;
}
return max;
}
时间复杂度:O(n)
空间复杂度:O(1)
三、总结
本次几个题目均是对撞指针的思路,这类题目关键是要能够把握
- 指针移动的条件
- 指针暂停移动的条件
- 指针结束移动的条件
一般条件3都是两个指针相遇则结束,而1、2两个条件则需要根据具体问题具体分析。