5.双指针

1.对撞指针

在头部和尾部分别设置一个指针,一个指针向后移动一个指针向前移动,如果指针对撞则停止循环

1.1.LeetCode 相关习题

2.LeetCode 题目

2.1_27移除元素

2.1.1 算法描述

这个题是比较典型的快慢指针,一个赋值指针 i 一个赋值指针 j ,j 是不断向前移动的,只有当 j 满足某个条件后将 nums[j] 的值赋给 nums[i] i 才会移动

2.1.2 代码实现

class Solution {
  public:
  int removeElement(vector<int>& nums, int val) {
    int i = 0; // 赋值指针
    int j = 0; // 移动指针
    while(j<nums.size()){
      if(nums[j]==val) j++;
      else nums[i++] = nums[j++];
    }
    return i;
  }
};

2.1.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

2.2_344 反转字符串

2.2.1 算法描述

start 从前向后, end 指针从后向前,交换两个指针指向的元素

2.2.2 代码实现

class Solution {
  public:
  void fanzhuan(vector<char>&s,int start,int end){
    while(start<end){
      swap(s[start++],s[end--]);
    }
  }
  void reverseString(vector<char>& s) {
    fanzhuan(s,0,s.size()-1);
  }
};

2.2.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

1.1.1 167.两数之和-输入有序数组

力扣题目链接

class Solution:
  def twoSum(self, numbers: List[int], target: int) -> List[int]:
    n = len(numbers)
    i = 0
    j = n - 1
    while i < j:
      if numbers[i] + numbers[j] == target:
        return [i+1, j+1]
      elif numbers[i] + numbers[j] > target:
        j -= 1
        else:
          i += 1
          return [i+1,j+1]

1.1.2 125 判断字符串是否回文

力扣题目链接

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        n = len(numbers)
        i = 0
        j = n - 1
        while i < j:
            if numbers[i] + numbers[j] == target:
                return [i+1, j+1]
            elif numbers[i] + numbers[j] > target:
                j -= 1
            else:
                i += 1
        return [i+1,j+1]

字符串涉及方法:

# 转换成大写
upper()
# 转换成小写
lower()
# 判断是否是字母或者数字
isalnum()

# 转换成大写 upper() # 转换成小写 lower() # 判断是否是字母或者数字 isalnum()

1.1.3 11 盛最多水的容器

class Solution:
  def maxArea(self, height: List[int]) -> int:
    max_v = 0
    i = 0
    j = len(height)-1
    while i<j:
      # 先计算当前面积
      volumn = (j-i)*min(height[i],height[j])
      max_v = max(max_v,volumn)
      #移动那根较短的柱子
      if height[i]<height[j]:
        i+=1
        else:
          j-=1
          return max_v

先计算再移动

判断大小涉及方法

# 判断两个数谁更小 min(a,b) # 判断两个数谁更大 max(a,b)

1.1.4 344-翻转字符串

力扣题目链接

class Solution:
  def reverseString(self, s: List[str]) -> None:
    """
        Do not return anything, modify s in-place instead.
        """
    i = 0
    j = len(s)-1
    while i<j :
      s[i],s[j] = s[j],s[i]
      i+=1
      j-=1

1.1.5 345-翻转字符串的元音字母

力扣题目链接

class Solution:
  def reverseVowels(self, s: str) -> str:
    array = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'] # 定义元音
    s_list = list(s) # str 2 list ,因为 str 没有 not in 和 in 方法
    i, j = 0, len(s)-1
    while i < j:
      if s_list[i] in array and s_list[j] in array:
        s_list[i], s_list[j] = s_list[j], s_list[i]
        i += 1
        j -= 1
        if s_list[j] not in array:
          j -= 1
          if s_list[i] not in array:
            i += 1
            return ''.join(s_list) # list 2 str

字符串转换相关方法

# 字符串转换成 list list(s) # list 转换成字符串 ‘’.join(s_list)

1.1.6 633 平方数之和

1

2.快慢指针

2.1 快慢指针原理

当一个跑得快的人和一个跑的慢的人在一个环形的赛道上赛跑,最后跑的快的人会赶上跑的慢的人

所以快指针一次走两步,慢指针一次走一步,最终会相遇在环的某个位置

这个方法用来判断一个链表中是否出现环

2.2 LeetCode 相关习题

2.2.1 142 环形链表

力扣题目链接

2.2.2解题思路

主要判断:

1.链表是否有环

①存在环:

如果 fast 指针和 slow 指针相遇了,则代表存在环

他们相遇的地方肯定是在环内的某个位置

相遇时 fast 比 slow 多走了 n 圈

②不存在环

fast 最后走向了 None

2.如果有环,环的入口 在哪

①现在假设存在环:

x = 从 head 节点到环的入环口的 step 数

y = 入环口到 fast 和 low 相遇的地方的 step 数

z = 相遇节点剩余部分再到入环口为 z

img

②相遇时两个节点走的步数

slow : x+y

fast:x+n(y+z)+y

因为快指针是慢指针的两倍:

fast = 2 slow

相关数学公式推导:

等式:2*(x+y)=x+y+n(y+z)

两边消掉一个(x+y): x + y = n (y + z)

因为求的是入口点的举例,所以求得x 的值:x = n (y + z) - y

在从n(y+z)中提出一个 (y+z)来,整理公式之后:x = (n - 1) (y + z) + z

这里的 n 是大于等于1 的,因为 fast 要至少走完 1 圈才会和 slow 相遇

当 n=1 时: x=z

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了

当 n>1 时这个等式仍然成立,因为我们所求的是位移,不是路程,所以上面等式和 fast 多绕了几圈是没有关系,不妨碍成立的

③代码设计思路

在存在闭环的情况下,在找到相遇的节点之后,**分别定义一个指针从原始出发,然后再定义一个指针从相遇节点出发,走相同的次数后,如果两个指针相遇则相遇的节点就是环的入口 **

2.2.3 C++ & Python 代码实现

(1)C++ 代码实现

class Solution {
  public:
  ListNode *detectCycle(ListNode *head) {
    // 判空做操作
    if(head==NULL||head->next==NULL) return NULL;
    ListNode* slow  = head;
    ListNode* fast = head;
    fast = fast->next->next; // 先走两步
    slow = slow->next;
    while(fast!=NULL&&fast->next!=NULL){
      // 判断是否相等:开始放 p,q 指针
      if(slow==fast){
        ListNode* p = head;
        ListNode* q = fast;
        // x,z 相遇的地方就是初始节点
        while(p!=q){
          p = p->next;
          q = q->next;
        }
        return p;
      }else{
        fast = fast->next->next;
        slow = slow->next;
      }
    } 
    return NULL;

  }
};

1.定义闭环链表(代码直接给出了)

Definition for singly-linked list.
class ListNode:
  def __init__(self, x):
    self.val = x
    self.next = None

2.方法主体

def detectCycle(self, head: ListNode) -> ListNode:
  fast,slow = head,head
  while fast and fast.next :
    fast = fast.next.next # 向前移动一步
    slow = slow.next # 向前移动两步
    # 如果两个指针指向的值相等,代表存在闭环
    if fast == slow: # 这里直接判断的是节点,不是节点里的值
      p = head
      q =slow
      # 当两个值相等的时候就要执行 x=z 的步骤了
      while p!=q:
        p = p.next
        q = q.next
        return p # 很奇怪这里返回 q 的结果会是错的

2.2_287寻找重复数

LeetCode题目链接

2.2.1 算法描述

1.快慢指针法

特殊性:数组中有 n 个位置,能放 1~n+1 的数字,那么根据 “生日原理”,至少有一个数一定是重复的,那么就可以组成一个环。

将这个特殊的数组当做一个双链表来看,即,将下标 index 和 元素值都看做是指针,就形成了 index->nums[index] 的映射,在这个映射上进行构图,就是一个环形链表

image-20211221082254994

然后这个题就会转换成上个题,现在就只需要找到环形链表的入口节点即可

image-20211221082634923

2.2.2 C++ 代码实现

class Solution {
  public:
  int findDuplicate(vector<int>& nums) {
    int slow = 0;
    int fast = 0;
    while(true){ // 这里当做循环链表处理
      fast = nums[nums[fast]];
      slow = nums[slow];
      if(fast==slow){
        int p = 0;
        int q = slow;
        while(p!=q){
          p = nums[p];
          q = nums[q];
        }
        return p;
      }
    }
  }
};

2.2.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(1)

3.滑动窗口

1.基本思想

滑动窗口中用到了左右两个指针,它们移动的思路是:以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,滑动窗口会变大,右指针走啊走直到滑动窗口的值不满足某个要求了。这时候开始移动左指针,使用 while 循环一直移动直到 left 满足为止。满足后 right 指针可以继续探索新的区域,寻找下一个满意的区间

left 指针的移动是在内部 while 循环中 ; right 指针的移动是当 left 指针满足要求了 right 在 while 循环的最外层移动

滑动窗口参考资料

2.模板

image-20220123134129855

1.定义两个指针 left ,right ,一起指向起始位置

2.第一重 while 循环让 right 进行移动,相当于对每个字符进行判断

3.第二重循环让 left 移动,将 left 移动到符合题意的那个 index 上

4.right 指针再向前一步探索新的区间

3.2_3无重复的最长子串

LeetCode 题目链接

3.3.1 算法描述

对于例子 abcabcbb 来说,有一个指针 left 需要不断的在字符串上滑动,滑滑滑滑到 abca 时发现产生了重复,

出现重复需要做两件事:

①将最左侧的 a 移走

②left++,也就是将下一个元素作为最长子串的起始位置

用什么判断是否产生了重复:set 判断。

这里需要双指针,一个指针指向非重复字符串的起始位置,一个指针不断的滑动。

image-20220122160834904

3.1.2 代码实现

class Solution {
  public:
  int lengthOfLongestSubstring(string s) {
    int left = 0;
    int right = 0;
    int res = 0;
    unordered_set<char> uset;
    while(right<s.size()){
      // 判断 set 中是否有重复的元素
      while(uset.find(s[right])!=uset.end()){
        uset.erase(s[right]);
        left++;
      }
      // 符合条件了,判断符合条件的结果
      res = max((right-left+1),res);
      uset.insert(s[right]);
      right++;

    }
    return res;

  }
};

3.1.3 时空复杂度

时间复杂度:O(N)

空间复杂度:O(∣Σ∣) Σ:字母 ASCII 码的合集

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值