对双指针的理解和总结

对双指针的理解和总结

前言

双指针在这周的题目中频繁出现,于是我尝试总结并归纳目前我所见到的双指针

简介

双指针是一种常用于解决数组、链表等线性结构问题的算法技巧。它通过使用两个指针在同一结构上操作,以减少时间复杂度,优化算法性能。双指针分为快慢指针和对撞指针,在做题时需要根据题目需要,判断双指针类型

快慢指针

27. 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。

假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k

用户评测:

评测机将使用以下代码测试您的解决方案:

int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
                            // 它以不等于 val 的值排序。

int k = removeElement(nums, val); // 调用你的实现

assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
    assert nums[i] == expectedNums[i];
}

如果所有的断言都通过,你的解决方案将会 通过

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。

提示:

  • 0 <= nums.length <= 100

  • 0 <= nums[i] <= 50

  • 0 <= val <= 100

思路

这是一道很经典的快慢指针在数组中的操作

快指针负责遍历数组中所有元素并与所需值作比较

慢指针负责写入新的处理好的数组

代码实现

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

环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos-1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例 1:

img

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

img

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

img

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

  • 链表中节点的数目范围在范围 [0, 104]
  • -105 <= Node.val <= 105
  • pos 的值为 -1 或者链表中的一个有效索引

**进阶:**你是否可以使用 O(1) 空间解决此题?

思路

使用快慢指针确定链表中是否有环

在快慢指针相遇后,将其中一个指针调回链表的头节点,再同步移动指针,两指针相遇的位置即为链表进入环的入口位置

代码实现

struct ListNode* detectCycle(struct ListNode* head) {
    struct ListNode* fast = head;
    struct ListNode* slow = head;
    
    // 检测是否有环
    while (fast != NULL && fast->next != NULL) {
        fast = fast->next->next;
        slow = slow->next;
        // 相遇,表示有环
        if (fast == slow) {
            fast = head;  // 将fast指针移到链表头部
            while (fast != slow) {  // fast和slow一起前进,直到相遇
                fast = fast->next;
                slow = slow->next;
            }
            return fast;  // 返回环的入口节点
        }
    }
    return NULL;
}

滑动窗口

滑动窗口是双指针中比较特殊的用法

不断调节左右指针,遍历这个窗口,判断子数组是否符合条件,输出

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

示例 1:

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

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

思路

创建滑动窗口,检测窗口中的元素是否符合要求,纪录窗口长度。

滑动窗口本质是根据窗口的数据,不断调整左右指针的位置,从而调整窗口大小,进行下一轮判断

代码实现

int minSubArrayLen(int target, int* nums, int numsSize) {
    int fast = 0, slow = 0;
    int sum = 0;
    int len = numsSize + 1;
    for (;fast < numsSize; fast++) {
        sum += nums[fast];
        while (sum >= target) {
            sum -= nums[slow];
            slow++;
            if (len > fast - slow + 1) {
                len = fast - slow + 1;
            }
        } 
    }
    if (len != numsSize + 1) {
        return len;
    }else {
        return 0;
    }
}

双指针对撞

双指针一个指向头,一个指向尾,然后向中间相向而行,直到双指针相遇,此时,遍历完成整个数组

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

提示:

  • 1 <= s.length <= 105
  • s[i] 都是 ASCII 码表中的可打印字符

思路

典型的对撞双指针,建立双指针,分别指向边界

指针向中间移动,交换两指针指向的值,直到两指针相遇

代码实现

void reverseString(char* s, int sSize) {
    int i,j;
    i = 0;
    j = sSize - 1;
    //直到两指针相遇
    while (i <= j) {
        //交换指针指向的值
        char temp = s[i];
        s[i] = s[j];
        s[j] = temp;
        i++;
        j--;
    }
}

977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

示例 1:

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

示例 2:

输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums 已按 非递减顺序 排序

进阶:

  • 请你设计时间复杂度为 O(n) 的算法解决本问题

思路

题目要求有序数组的平方,但是要求平方后任然有序

不难想到实际上数组两边的数平方后是最大的数

建立双指针,一个指向头一个指向尾

平方后比较大小,倒序插入数组中

代码实现

int* sortedSquares(int* nums, int numsSize, int* returnSize) {
    *returnSize = numsSize;
    //双指针指向数组两边
    int left = 0;
    int right = numsSize - 1;
    int* result = (int* )malloc(sizeof(int) * numsSize);
    //指向要插入的位置
    int p = numsSize - 1;
    while (left <= right) {
        int squarel = nums[left] * nums[left];
        int squarr = nums[right] * nums[right];
        //判断平方后的数组那个大,并插入数组
        if (squarel > squarr) {
            result[p] = squarel;
            left++;
            p--;
        } else {
            result[p] = squarr;
            right--;
            p--;
        }
    }
    return result;
}

总结

双指针其实是代替双循环从而降低时间复杂度,双指针的本质是通过两个指针在同一个数据结构上高效地并行操作,利用它们的相对位置关系来优化问题的求解过程。它通过减少不必要的重复计算,提高程序效率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值