Leetcode刷题记录之双指针
167. 两数之和 II - 输入有序数组#
题目描述#
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
和 index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
输入输出#
输入: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 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
题解#
-
快慢指针,慢指针 slow 每次走 1 步,快指针 fast 每次走两步,如果两指针能够相遇,证明链表有环,否则如果快指针无路可走,证明链表无环。
-
当快慢指针相遇时,令快指针重新回到起点,慢指针原地不动,此时快慢指针每次都走一步。当两指针再次相遇时,就是环的起始节点。
证明:
设环的长度是 C,入环之前的长度是 L,当两指针第一次相遇时:
慢指针走过的长度:S = L + M
快指针走过的长度:F = L + M + kC。(假设快指针已经走过了k圈)
因为快指针走过的长度是慢指针的两倍,则 F = 2S。
L + M + kC = 2L + 2M
(k - 1)C + (C - M) = L
所以第 2 步是正确的。
代码#
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
动态维护窗口中所有的字符以及它们的个数。在 left
与 right
指针移动的过程中,动态维护一个 count
变量,这个 count
表示当前窗口中所包含的满足条件的字符个数,当 count == t_size
时,当前窗口已包含 t 中所有元素。
什么样的字符是满足条件的字符呢?
- 当考虑把一个字符加进滑动窗口时,如果当前滑动窗口中同类字符的个数小于
t
数组中同类字符的个数时,该字符是满足条件的,应该自增count
。 - 当考虑把一个字符移出滑动窗口时,如果移出之后滑动窗口中的同类字符个数小于
t
数组中同类字符的个数时,说明移出的字符是满足条件的,应该自减count
。
比如:s = A A A A B B B C
,t = 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
,你要判断是否存在两个整数 a
和 b
,使得 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 挪到数组的尾部,具体的方法为:
-
分别使用两根指针,
pos0
指向下一个0
应该存放的位置,pos2
指向下一个2
应该存放的位置。 -
如果当前数值是
0
,那就让它交换到左边; -
否则如果当前数值是
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 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)