Leetcode刷题记录之双指针

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

题目描述#

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1]numbers[index2] ,则 1 <= index1 < index2 <= numbers.length

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1index2

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

输入输出#

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

题解#

使用双指针,左指针指向数组开头,右指针指向数组结尾。

  • 如果当前两数之和大于 target,则将右指针向左移动。
  • 如果当前两数之和小于 target,则将左指针向右移动。

代码#

class Solution {
public:
  vector<int> twoSum(vector<int>& numbers, int target) {
    int sz = static_cast<int>(numbers.size());
    int left = 0, right = sz - 1;
    while (left < right) {
      int sum = numbers[left] + numbers[right];
      if (sum == target) return {left + 1, right + 1};
      else if (sum > target) --right;
      else ++left;
    }
    return {};
  }
};

刷题记录#

一刷 2022年8月1日

88. 合并两个有序数组#

题目描述#

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

输入输出#

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

题解#

把两个指针分别放在两个数组的末尾,每次将较大的那个数字复制到 nums1 的后边,然后向前移动一位。

代码#

class Solution {
public:
  void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    int ptr1 = m - 1, ptr2 = n - 1, ptr3 = m + n - 1;
    while (ptr2 >= 0) {
      if (ptr1 == -1 || nums1[ptr1] < nums2[ptr2]) nums1[ptr3--] = nums2[ptr2--];
      else nums1[ptr3--] = nums1[ptr1--];
    }
  }
};

刷题记录#

一刷 2022年8月1日

142. 环形链表 II#

题目描述#

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

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

不允许修改 链表。

输入输出#

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

题解#

  1. 快慢指针,慢指针 slow 每次走 1 步,快指针 fast 每次走两步,如果两指针能够相遇,证明链表有环,否则如果快指针无路可走,证明链表无环。

  2. 当快慢指针相遇时,令快指针重新回到起点,慢指针原地不动,此时快慢指针每次都走一步。当两指针再次相遇时,就是环的起始节点。

证明:

设环的长度是 C,入环之前的长度是 L,当两指针第一次相遇时:

慢指针走过的长度:S = L + M

快指针走过的长度:F = L + M + kC。(假设快指针已经走过了k圈)

因为快指针走过的长度是慢指针的两倍,则 F = 2S。

L + M + kC = 2L + 2M

(k - 1)C + (C - M) = L

所以第 2 步是正确的。

image-20220802154639726

代码#

class Solution {
public:
  ListNode *detectCycle(ListNode *head) {
    if (head == nullptr) return nullptr;
    ListNode *fast = head, *slow = head;
    do {
      // 快指针每次走两步,慢指针每次走一步
      if (fast->next == nullptr || fast->next->next == nullptr) {
        // 快指针可以走到尽头,证明无环
        return nullptr;
      }
      fast = fast->next;
      fast = fast->next;
      slow = slow->next;
    } while (fast != slow);

    // 有环,寻找环的起始节点
    fast = head;
    while (fast != slow) {
      fast = fast->next;
      slow = slow->next;
    }
    return fast;
  }
};

刷题记录#

一刷 2022年8月2日

76. 最小覆盖子串#

题目描述#

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

输入输出#

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

题解#

我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 right 指针,和一个用于「收缩」窗口的 left 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在字符串 s 上滑动窗口,通过移动 right 指针不断扩张窗口。当窗口包含字符串 t 全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。

关键难点在于如何判断当前窗口是否包含字符串 t 全部所需的字符?

用一个哈希表表示 t 中所有的字符以及它们的个数,用一个哈希表动态维护窗口中所有的字符以及它们的个数,每次判断当前窗口是否可行时,遍历比较这两个哈希表。

但这种方法比较低效。

更高效的方法:

同样使用两个哈希表,一个哈希表 t_freq 表示 t 中所有的字符以及它们的个数,令一个哈希表 window_freq 动态维护窗口中所有的字符以及它们的个数。在 leftright 指针移动的过程中,动态维护一个 count 变量,这个 count 表示当前窗口中所包含的满足条件的字符个数,当 count == t_size 时,当前窗口已包含 t 中所有元素。

什么样的字符是满足条件的字符呢?

  • 当考虑把一个字符加进滑动窗口时,如果当前滑动窗口中同类字符的个数小于 t 数组中同类字符的个数时,该字符是满足条件的,应该自增 count
  • 当考虑把一个字符移出滑动窗口时,如果移出之后滑动窗口中的同类字符个数小于 t 数组中同类字符的个数时,说明移出的字符是满足条件的,应该自减 count

比如:s = A A A A B B B Ct = A B B C

如果当前窗口是 A A A A,则 count = 1

向窗口中加入 B,当前窗口是 A A A A B,则 count = 2

向窗口中加入 B,当前窗口是 A A A A B B,则 count = 3

向窗口中加入 B,当前窗口是 A A A A B B B,则 count 不变,还是 3。

向窗口中加入 C,当前窗口是 A A A A B B B C,则 count = 4,当前窗口已包含 t 中所有元素。

向窗口中移出 A,当前窗口是 A A A B B B C,则 count = 4,当前窗口已包含 t 中所有元素。

向窗口中移出 A,当前窗口是 A A B B B C,则 count = 4,当前窗口已包含 t 中所有元素。

向窗口中移出 A,当前窗口是 A B B B C,则 count = 4,当前窗口已包含 t 中所有元素。

向窗口中移出 A,当前窗口是 B B B C,则 count = 3,当前窗口无法包含 t 中所有元素。

代码#

class Solution {
public:
  string minWindow(string s, string t) {
    int s_size = static_cast<int>(s.size());
    int t_size = static_cast<int>(t.size());
    // 'z' 的ANSI是122
    std::vector<int> window_freq(123, 0);
    std::vector<int> t_freq(123, 0);
    // 初始化频度数组
    for (const char c: t) {
      ++t_freq[c];
    }
    // 初始化min_len = s.size() + 1,表示s中没有符合条件的子字符串
    int left = 0, right = 0, count = 0, min_len = s_size + 1, min_left = 0;
    for (; right < s_size; ++right) {
      // 向当前窗口中新加入right指向的元素
      if (t_freq[s[right]] > 0 && window_freq[s[right]]++ < t_freq[s[right]]) {
        ++count;
      }
      // 当前窗口已包含t中所有元素,在满足条件的情况下尝试缩小窗口
      while (count == t_size) {
        if (min_len > right - left) {
          min_len = right - left + 1;
          min_left = left;
        }
        if (t_freq[s[left]] > 0 && window_freq[s[left]]-- == t_freq[s[left]]) --count;
        ++left;
      }
    }
    return min_len == s_size + 1 ? "" : s.substr(min_left, min_len);
  }
};

刷题记录#

一刷 2022年8月3日

633. 平方数之和#

题目描述#

给定一个非负整数 c ,你要判断是否存在两个整数 ab,使得 a2 + b2 = c

输入输出#

输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5

题解#

有两点需要注意:

  • 使用 long long 防止越界。
  • 右指针 right 初始应该指向 sqrt(c)

代码#

class Solution {
public:
  bool judgeSquareSum(int c) {
    long long target = c;
    long long left = 0, right = static_cast<int>(std::sqrt(c));
    while (left <= right) {
      long long product = left * left + right * right;
      if (product == target) return true;
      else if (product < target) ++left;
      else --right;
    }
    return false;
  }
};

刷题记录#

一刷 2022年8月4日

680. 验证回文字符串 Ⅱ#

题目描述#

给定一个非空字符串 s最多删除一个字符。判断是否能成为回文字符串。

输入输出#

输入: s = "aba"
输出: true

题解#

左指针 left 从前往后移动,右指针 right 从后往前移动,一旦发现 s[left] != s[right],如果唯一的删除次数没有使用的话,那么有两种选择,要么删除 left 指向的元素,要么删除 right 指向的元素。

代码#

class Solution {
public:
  bool validPalindrome(string s) {
    return validPalindrome(s, 0, s.size() - 1, true);
  }
  // can_remove表示是否还可以进行删除动作
  bool validPalindrome(const string &s, int left, int right, bool can_remove) {
    if ((left == right) || (left == right - 1 && s[left] == s[right])) return true;
    if (s[left] == s[right]) {
      return validPalindrome(s, left + 1, right - 1, can_remove);
    } else {
      // 唯一的一次删除次数还没有使用
      if (can_remove) {
        return validPalindrome(s, left + 1, right, false) 
        || validPalindrome(s, left, right - 1, false);
      } else {
        return false;
      }
    }
  }
};

刷题记录#

一刷 2022年8月4日

524. 通过删除字母匹配到字典里最长单词#

题目描述#

给你一个字符串 s 和一个字符串数组 dictionary ,找出并返回 dictionary 中最长的字符串,该字符串可以通过删除 s 中的某些字符得到。

如果答案不止一个,返回长度最长且字母序最小的字符串。如果答案不存在,则返回空字符串。

输入输出#

输入:s = "abpcplea", dictionary = ["ale","apple","monkey","plea"]
输出:"apple"

题解#

关键是要判断 dictionary 中的字符串 t 是否是 s 的子序列。使用双指针进行判断即可。

初始化两个指针 i 和 j,分别指向 t 和 s 的初始位置。每次贪心地匹配,匹配成功则 i 和 j 同时右移,匹配 t 的下一个位置,匹配失败则 j 右移,i 不变,尝试用 s 的下一个字符匹配 t。

代码#

class Solution {
public:
    string findLongestWord(string s, vector<string>& dictionary) {
        string res;
        for (const string &str : dictionary) {
            if (isMatch(s, str)) {
                if (str.size() > res.size()) res = str;
                else if (str.size() == res.size()) res = str < res ? str : res;
            }
        }
        return res;
    }

private:
    bool isMatch(const string &str, const string &sub) {
        int str_sz = static_cast<int>(str.size());
        int sub_sz = static_cast<int>(sub.size());
        int i = 0, j = 0;
        while (i < str_sz && j < sub_sz) {
            if (str[i] == sub[j]) {
                ++i;
                ++j;
            } else {
                ++i;
            }
        }
        return j == sub_sz;
    }
};

刷题记录#

一刷 2022年8月5日

75. 颜色分类#

题目描述#

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

必须在不使用库的sort函数的情况下解决这个问题。

输入输出#

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

题解#

在一次遍历中,把 0 挪到数组的头部,2 挪到数组的尾部,具体的方法为:

  1. 分别使用两根指针,pos0 指向下一个 0 应该存放的位置,pos2 指向下一个 2 应该存放的位置。

  2. 如果当前数值是 0,那就让它交换到左边;

  3. 否则如果当前数值是 1,那就让它交换到右边。注意,如果从右边交换回来的是 0 或者 2,要继续进行交换,否则交换回来的是 0 或者 2 就再也没法移动到其准确的位置了。

代码#

class Solution {
public:
  void sortColors(vector<int>& nums) {
    int nums_size = static_cast<int>(nums.size());
    // pos0是下一个0应该存放的位置,pos2是下一个2应该存放的位置
    int pos0 = 0, pos2 = nums_size - 1, i = 0;
    while (i <= pos2) {
      if (nums[i] == 0) {
        std::swap(nums[i], nums[pos0++]);
      } else if (nums[i] == 2) {
        std::swap(nums[i], nums[pos2--]);
        // 从右边换回来的是0或者2,要继续交换
        if (nums[i] != 1) continue;
      }
      ++i;
    }
  }
};

刷题记录#

一刷 2022年8月14日

作者:lmcoding

出处:https://www.cnblogs.com/lmcoding/p/16542218.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   cclemontree  阅读(37)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示