[LeetCode算法->双指针]

在算法中,双指针的问题较为常见,应用也比较广泛,双指针问题能够降低时间复杂度和空间复杂度,有必要掌握这一内容。

下面通过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]

对于旋转的题目,只需要记住最优解法即可。

最优解是:逆序

该方法远比双指针来得更加简单粗暴。

  1. 后k个逆序

  1. 前numsSize个逆序

  1. 整体逆序

想想是不是这样。

所谓的逆序,也是第一个下标的元素和最后一个下标的元素交换位置即可

交换完第一个和最后一个元素后,再交换第二个元素和倒数第二个元素,以此类推

代码实现:

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;
}

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓富民

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值