您现在的位置是:首页 >技术杂谈 >代码随想录一刷总结(leetcode解题整理)网站首页技术杂谈
代码随想录一刷总结(leetcode解题整理)
代码随想录算法训练营一刷总结
不知不觉两个月过去了,陆陆续续还是把代码随想录一刷结束,很早之前就知道代码随想录的存在,但是一直没有能一股气把所有题目过一遍,刚开学的时候正好看到算法训练营的报名,想着趁此机会让自己的算法刷题踏上正轨,作为结尾,把这段时间的题目好好整理一遍思路,作为后面刷题的基石
我的整理方式是模拟实战做题时的感受,主要想法是提取问题本质尽可能化为具象的数学模型
往后会持续更新,主要是记录实战过程中遇到的题目将其归类到后面的思想方法中去。
二分查找
解决问题:
给定一个
n
n
n 个元素有序的(升序)整型数组
n
u
m
s
nums
nums 和一个目标值
t
a
r
g
e
t
target
target ,写一个函数搜索
n
u
m
s
nums
nums 中的
t
a
r
g
e
t
target
target,如果目标值存在返回下标,否则返回 -1
二分查找的意义在于通过二分将遍历的复杂度
O
(
n
)
O(n)
O(n)降低为
O
(
log
2
n
)
O(log_2n)
O(log2n),故二分的优化意义大于对问题的解决意义
借助题目展现代码细节
704. 二分查找
在实现代码的过程中用到了循环不变量的概念,在这里体现为区间,我们整个过程中查找的区间的定义是什么?如果我们的定义是
[
l
e
f
t
,
r
i
g
h
t
]
[left,right]
[left,right],那么此时的代码实现为:
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2; //关于middle的两种写法在之前的文章有讨论
if (nums[middle] == target) return middle;
else if (nums[middle] > target) {
right = middle - 1;
}
else {
left = middle + 1;
}
}
return -1;
}
};
关于
m
i
d
d
l
e
middle
middle的两种写法
再借助一道其他题目体会
34. 在排序数组中查找元素的第一个和最后一个位置
题目描述:
给你一个按照非递减顺序排列的整数数组
n
u
m
s
nums
nums,和一个目标值
t
a
r
g
e
t
target
target。请你找出给定目标值在数组中的开始位置和结束位置,如果数组中不存在目标值
t
a
r
g
e
t
target
target,返回
[
−
1
,
−
1
]
[-1, -1]
[−1,−1]。你必须设计并实现时间复杂度为
O
(
l
o
g
k
n
)
O(log_kn)
O(logkn)的算法解决此问题。
我们的关注点需要集中于如何将本题转化为原有题目的模型
原问题
⟺
iff
⟺查找目标值的区间
⟺
iff
⟺查找第一个小于目标值与第一个大于目标值的位置
如是我们明白二分查找真正常用的是查找特定区间而非特定值
我们尝试去推导这个问题:
记住一句话,二分查找本质是分而治之,我们不仅要关心查找的位置,更要关心这个位置两边元素的特征,以左闭右闭为例,
r
i
g
h
t
right
right代表的是在其右边(包括自己)的元素都大于等于
t
a
r
g
e
t
target
target(此处理论上是大于,但是在区间问题中应有所变化),
l
e
f
t
left
left代表的是在其左边的元素(包括自己)都小于
t
a
r
g
e
t
target
target,而这个特点在整个循环过程中没有发生改变,我们能否利用这一点推导出我们想要的区间位置?
很显然最后的结果一定是
l
e
f
t
left
left的位置即是第一个
t
a
r
g
e
t
target
target的位置
同理当我们改变
l
e
f
t
left
left和
r
i
g
h
t
right
right的含义之后,我们可以得到类似的结果
那么我们得到代码实现结果:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int left = 0, right = nums.size() - 1;
vector<int> result(2, -1);
bool tmp = false;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] == target) {
tmp = true;
break;
}
else if (nums[middle] > target) {
right = middle - 1;
}
else {
left = middle + 1;
}
}
if (!tmp) return result;
left = 0;
right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] >= target) {
right = middle - 1;
}
else {
left = middle + 1;
}
}
result[0] = left;
left = 0;
right = nums.size() - 1;
while (left <= right) {
int middle = (left + right) / 2;
if (nums[middle] > target) {
right = middle - 1;
}
else {
left = middle + 1;
}
}
result[1] = right;
return result;
}
};
双指针
双指针法(快慢指针法)在数组和链表的操作中非常常见
双指针法本质是改变了遍历方式,使得遍历过程无回溯,故而降低了时间复杂度,我们不妨将数组的一般遍历看作
r
i
g
h
t
right
right指针不动的特殊双指针
1.同向双指针
2.异向双指针
例题:345. 反转字符串中的元音字母
给你一个字符串 s s s ,仅反转字符串中的所有元音字母,并返回结果字符串。元音字母包括 ′ a ′ , ′ e ′ , ′ i ′ , ′ o ′ , ′ u ′ 'a','e','i','o','u' ′a′,′e′,′i′,′o′,′u′,且可能以大小写两种形式出现不止一次。
反转元音字母
⟺
iff
⟺反转序列中的特殊字符
⟺
iff
⟺反转序列
在此题中,我们需要改变的无非是增加一些
l
e
f
t
left
left与
r
i
g
h
t
right
right指针的移动条件
简而言之,双指针的问题的关键就在于指针移动的条件
class Solution {
public:
string reverseVowels(string s) {
int left = 0, right = s.size() - 1;
unordered_set<char> hashset = {'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'};
while (left <= right) {
if (hashset.count(s[left]) <= 0) {
left++;
}
else if (hashset.count(s[right]) <= 0) {
right--;
}
else {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
return s;
}
};