数据结构算法---双指针

算法核心思想:
双指针多用于遍历数组,可以同时遍历一个数组或分别遍历不同的数组。常见的设计思想如下:
对于排序后的数组,双指针分别指向数组的头尾,遍历方向相反,用于搜索。
双指针指向两个数组,分别遍历
快慢指针
滑动窗口

两数之和
167. Two Sum II - Input array is sorted (Easy)
题目描述

在一个增序的整数数组里找到两个数,使它们的和为给定值。已知有且只有一对解。

输入输出样例

输入是一个数组(numbers)和一个给定值(target)。输出是两个数的位置,从 1 开始计数。
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]

在这个样例中,第一个数字(2)和第二个数字(7)的和等于给定值(9)。

核心思路:两个指针分别指向数组的头尾,相向而行遍历,若判断结果大于则增加左指针,若小于则减小右指针

class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) 
    {
        vector<int>::iterator p_front = numbers.begin();
        vector<int>::reverse_iterator p_behind = numbers.rbegin();
        int first = 1;
        int end = numbers.size();

        while (1<end)
        {
            if (((*p_front )+( *p_behind)) == target)
            {
                return vector<int>{ first,end };
            }

            if ((*p_front + *p_behind) < target)
            {
                p_front++;
                first++;
            }
            if ((*p_front + *p_behind) > target)
            {
                p_behind++;
                end--;
            }


        }


    }
};


int main()
{
    vector<int> numbers{ 2,3,5,8,10,11 };
    Solution s;
    vector<int> ret;
    ret= s.twoSum(numbers, 8);
    cout << ret[0] << " " << ret[1] << endl;

}

归并两个有序数组

  1. Merge Sorted Array (Easy)

题目描述

给定两个有序数组,把两个数组合并为一个。

输入输出样例

输入是两个数组和它们分别的长度 m 和 n。其中第一个数组的长度被延长至 m + n,多出的
n 位被 0 填补。题目要求把第二个数组归并到第一个数组上,不需要开辟额外空间。
Input: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
Output: nums1 = [1,2,2,3,5,6]

核心思想:两个指针分别遍历两个数组进行比较,每次将较大的放到第一个数组的末尾,并自减,第三个指针用来管理复制,如果第二个数组先复制完了,则剩下的数组不需要改变,若第一个数组先复制完,则将第二个数组剩余的元素放到第一个数组的头部

#include <iostream>
#include <vector>
#include<algorithm>
#include <numeric>
using namespace std;


class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n)
    {
        int pos = m-- + n-- - 1;
        while (m >= 0 && n >= 0)
        {
         //   nums1[pos--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--];
            nums1[pos--] = (nums1[m] > nums2[n]) ? nums1[m--] : nums2[n--];
        }
        while (n >= 0)
        {
            nums1[pos--] = nums2[n--];
        }
    }
};


int main()
{
    vector<int> nums1{ 1,2,3,0,0,0 };
    vector<int> nums2{ 2,5,6 };
    Solution s;

    s.merge(nums1, 3, nums2,nums2.size());

    for (int i = 0; i < nums1.size(); i++)
    {
        cout << nums1[i] << " ";
       
    }
}



快慢指针
142. Linked List Cycle II (Medium)

题目描述
给定一个链表,如果有环路,找出环路的开始点。

输入输出样例
输入是一个链表,输出是链表的一个节点。如果没有环路,返回一个空指针。

在这个样例中,值为 2 的节点即为环路的开始点。
如果没有特殊说明, LeetCode 采用如下的数据结构表示链表。

struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};

核心思想:如果两个人同时出发,如果赛道有环,那么快的一方总能追上慢的一方。进一步想,追上时快的一方肯定比慢的一方多跑了几圈,即多跑的路的长度是圈的长度的倍数。
原理:设起点到环的起点距离为m,已经确定有环,环的周长为n,(第一次)相遇点距离环的起点的距离是k。那么当两者相遇时,慢指针移动的总距离为i,i = m + a * n + k,因为快指针移动速度为慢指针的两倍,那么快指针的移动距离为2i,2i = m + b * n + k。其中,a和b分别为慢指针和快指针在第一次相遇时转过的圈数。我们让两者相减(快减慢),那么有i = (b - a) * n。即i是圈长度的倍数。利用这个结论我们就可以理解Floyd解法为什么能确定环的起点。将一个指针移到链表起点,另一个指针不变,即距离链表起点为i处,两者同时移动,每次移动一步。当第一个指针前进了m,即到达环起点时,另一个指针距离链表起点为i + m。考虑到i为圈长度的倍数,可以理解为指针从链表起点出发,走到环起点,然后绕环转了几圈,所以第二个指针也必然在环的起点。即两者相遇点就是环的起点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution 
{
public:
    ListNode *detectCycle(ListNode *head) 
    {
         ListNode *fast=head;
         ListNode *slow=head;
        do {
                if (!fast || !fast->next) return nullptr;
                fast = fast->next->next;
                slow = slow->next;
            } while (fast != slow);
         
         fast=head;
         while(fast!=slow)
         {
             fast=fast->next;
             slow=slow->next;
         }

        return fast;
    }
};

滑动窗口
76. Minimum Window Substring (Hard)

题目描述
给定两个字符串 S 和 T,求 S 中包含 T 所有字符的最短连续子字符串的长度,同时要求时间
复杂度不得超过 O„n”。

输入输出样例
输入是两个字符串 S 和 T,输出是一个 S 字符串的子串。
Input: S = “ADOBECODEBANC”, T = “ABC”
Output: “BANC”
在这个样例中, S 中同时包含一个 A、一个 B、一个 C 的最短子字符串是“BANC”。

核心思想:维护一个动态变化的滑动窗口,寻找包含字符串T中所有元素的最小解。

滑动规则:1.使右边界增大,直到包含所有的T中所有的元素。2.缩小左边界,直到不够包含所有元素。此时为局部最优解。
3.让左边界加1,使其不满足条件,然后右边界继续增加直到又再次满足条件。4.重复123步,直到右边界越界。

这个题有点难写,先放一个别人的题解

class Solution {
public:
    string minWindow(string s, string t) {
        // 滑动窗口,首先将字符存储到哈希表中,然后再开始标准滑动窗口

        // 1: 字符存储, 标记存储
        // 为何要存储标记? 以 s = "ADOBECODEBANC", t = "ABC" 为例,如果s[left] = 'A' , 那么left右移的时候计数要归位
        vector<int> chars(128, 0);
        vector<int> flags(128, 1);
        for (char& c : t) {
            ++chars[c];
            flags[c] = 1;
        }

        // 2: 开滑!
        int left = 0, right = 0, n = s.size(), m = t.size(), cnt = 0;   // 滑动窗口基本变量
        int min_left = 0, min_size = INT_MAX;                           // 返回string的要求变量
        while (right < n) {
            if (--chars[s[right++]] >= 0)                                // right指针右移
                ++cnt;

            while (cnt >= m) {                                           // 滑动窗口判断+left左移
                if (right - left < min_size) {
                    min_left = left;
                    min_size = right - left;
                }

                if (flags[s[left]] && ++chars[s[left]] > 0)              // 存储标记发挥作用的时候
                    --cnt;

                left++;
            }
        }

        return min_size > n ? "" : s.substr(min_left, min_size);
    }
};


    int main()
    {
        string s = { "ADOBECODEBANC" };
        string t = { "ABC" };

        string out;
        Solution output;
        out = output.minWindow(s, t);


        for (const auto& p : out)
        {
            cout << p << endl;
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值