在算法中,双指针的问题较为常见,应用也比较广泛,双指针问题能够降低时间复杂度和空间复杂度,有必要掌握这一内容。
下面通过LeetCode的题目来说明双指针。
1.给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
解析:这道题可以通过双指针来求解,即给定i和j,分别指向0位置和numsize -1的位置, 比较两个指针所指向的元素的平方的大小,大的逆序放在要返回的数组中。为什么要逆序呢?
由于已知数组是非递减顺序排序的:
假如输入的数组全是非负数,那么平方后也是一个递增顺序。
假如输入的数组全是负数,那么平方后是一个递减的顺序。
假如输入的数组有负数,有非负数,那么平方后的情况就尾部是递减或递增的。
所以逆序存放平方后大的元素,就不再需要讨论上面的情况。
图解如上:
这样就可以解决问题了。
代码如下:
int* sortedSquares(int* nums, int numsSize, int* returnSize)
{
assert(nums);
*returnSize = numsSize;
int *ret = (int*)malloc(sizeof(int)*numsSize);
int i =0;
int j =numsSize -1;
while(numsSize--)
{
if(nums[i]*nums[i]<nums[j]*nums[j])
{
ret[numsSize] = nums[j]*nums[j];
j--;
}
else
{
ret[numsSize] = nums[i]*nums[i];
i++;
}
}
return ret;
}
2.给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
输入: nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
对于旋转的题目,只需要记住最优解法即可。
最优解是:逆序
该方法远比双指针来得更加简单粗暴。
后k个逆序
前numsSize个逆序
整体逆序
想想是不是这样。
所谓的逆序,也是第一个下标的元素和最后一个下标的元素交换位置即可
交换完第一个和最后一个元素后,再交换第二个元素和倒数第二个元素,以此类推
代码实现:
void reverse(int*left,int *right)
{
while(left<right)
{
int tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k)
{
if(numsSize==1)
{
reverse(nums,nums);
}
else if (numsSize ==2)
{
if(k%2!=0)
{
reverse(nums,nums+1);
}
else
reverse(nums,nums);
}
else
{
k%=numsSize;
reverse(nums+numsSize-k,nums+numsSize-1);//后k个逆序
reverse(nums,nums+numsSize-k-1);//前numsSize -k
reverse(nums,nums+numsSize-1);//整体
}
}
k%=numsSize的原因是,假如旋转8次,有6个元素,其效果和旋转两次是一样的。
3.给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
输入: nums = [0,1,0,3,12]
输出:[1,3,12,0,0]
解析:
本题使用双指针也是非常好的方法。
给一个指针指向下标0位置,该指针用来记录非0元素的个数,给另一个指针,该指针一次次往后遍历,如果往后遍历的指针!= 0 ,那么就把这个元素赋值给第一个位置的元素,然后各自往后移。
如果往后遍历的指针 == 0,那么继续往后遍历,第一个指针不用走。
这样遍历下去,最后第一个指针指向的位置就是最后一个非0元素的下标,然后把 后面的元素全都赋值成0即可。
代码如下:
int cmp(const void*e1,const void*e2)
{
return *(int*)e1 - *(int*)e2;
}
void moveZeroes(int* nums, int numsSize)
{
assert(nums);
int i =0;
int j =0;//记录0个数
for(i=0;i<numsSize;i++)
{
if(nums[i]!=0)
{
nums[j++] = nums[i];
}
}
while(j<numsSize)
{
nums[j++] = 0;
}
}
4.给你一个下标从 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] 。
解析:
在这道题中,我们要寻找的是符合条件的一对下标 (i,j)它们需要满足的约束条件是:
i、j都是合法的下标,即 0≤i<n,0≤j<n
i<j(题目要求)
而我们希望从中找到满足 arr[i] + arr[j] == target 的下标 (i,j)。以 n=5 为例,这时候全部的搜索空间是:
我们从右上角开始比较:
当arr[0] +arr[4] >target时,此时arr[0]是所在行中最小的,既然最小的+arr[4] 都> target,那么后面的arr[1]+ arr[4] ,arr[2] + arr[4] ...都大于target,所以应该找arr[0] +arr[3]的元素 ,此时就可以排除一列元素了,即j--。如下图:
当arr[0] +arr[4] <target 时,既然arr[4]是一列中最大的+arr[0] 都<target,那么arr[0] +arr[3],
arr[0]+arr[2]...也仍然小于target。所以应该排除掉一行元素,即i++。如下图:
代码如下:
int* twoSum(int* numbers, int numbersSize, int target, int* returnSize){
*returnSize = 2;
assert (numbers);
int i =0;
int j =numbersSize -1;
int *ret = (int*)malloc(sizeof(int)*(*returnSize));
while(i<j)
{
if(numbers[i]+numbers[j]<target)
{
i++;
}
else if(numbers[i]+numbers[j]>target)
{
j--;
}
else
{
ret[0] = i+1;
ret[1] = j+1;
return ret;
}
}
ret[0] = -1;
ret[1] = -1;
return ret;
}