以前学过二分与双指针,所以这算是复习
1.二分
1.1 一般二分
题目链接: 704. 二分查找
我的做法:二分
class Solution {
public:
int search(vector<int>& nums, int target)
{
int l = 0;
int r = nums.size() - 1;
// 我的目标 找大于等于target 的左边界
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] >= target)r = mid ;
else l = mid + 1;
}
if(nums[l] == target)return l;
return -1;
}
};
- 板子两个划线部分背。这里用的是左闭右开的。
- 理解二分用途:找一个区间,比如>= target、 >target、<= target 、<target的区间。
- 分清楚check()里面 ,一般都是 check(mid) ---> nums[mid]是不是左端点或者右端点?
- 对于mid=l+r(+1) 判断的标准看在if-else段中, l 还是 r =mid,不是看 if()l/r 是l还是r !!!
1.2 二分拓展
// 我的版本:
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
int l = 0;
int r = nums.size() - 1;
// 目标:找到第一个 >= target的下标
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
if(nums[l] == target)return l;
else if(nums[l] < target) return l+1;
else return l;
}
};
/*
[1,3,5,6]
0 0 0
2 0 1
4 1 2
7 3 3
*/
变形题目时,在最后return时候我需要写出来几种特殊情况,每种情况分析再找规律。
应该 1.写出3种情况 2.针对每种情况写代码
SXL的思路:
我二刷时候的思路:比较清晰了:
两个二分的思路:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
vector<int> out;
// 容易想到用两次二分 第一次>= target 的临界下标 + 第二次 <= target的临界下标
// 【1】 第一次 >= target 的临界下标
int l = 0;
int r = nums.size() - 1;
while(l < r)
{
int mid = (l + r) >> 1;
if(nums[mid] >= target) r = mid;
else l = mid + 1;
}
out.push_back(-1);
out.push_back(-1);
int l1 = l;
if(nums.size() == 0 || nums[l] != target)return out;
// 【2】 第一次 <= target 的临界下标
l = 0;
r = nums.size() - 1;
while(l < r)
{
int mid = (l + r + 1) >> 1;
if(nums[mid] <= target) l = mid;
else r = mid - 1;
}
out.pop_back();
out.pop_back();
out.push_back(l1);
out.push_back(l);
return out;
}
};
2.双指针
2.1普通双指针 ---》27. 移除元素
暴力做法:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
//暴力解法 多开一个vector<int> 没有原地
vector<int> tmp;
for(int i = 0; i < nums.size();i++)
if(nums[i] != val) tmp.push_back(nums[i]);
nums.clear();
for(int i = 0; i < tmp.size();i++)
nums.push_back(tmp[i]);
return nums.size();
}
};
内存占用太多啦!
我的双向双指针做法:【好吧--内存还是占8.5MB??】
class Solution {
public:
int removeElement(vector<int>& nums, int val)
{
// 解法2 ---- 双指针 把val全都移到后面
int l = 0;
int r = nums.size() - 1;
while( l < r)
{
if(nums[l] == val && nums[r] != val)
{
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
l++;
r--;
}
else if(nums[l] == val && nums[r] == val)
r--;
else if(nums[l] != val && nums[r] == val)
{
l++;
r--;
}
else if(nums[l] != val && nums[r] != val)
l++;
}
for(int i = nums.size() - 1; i >= 0;i--)
if(nums[i] == val)nums.pop_back();
return nums.size();
}
};
看了答案,carl的做法是单向双指针,快指针是找新数组的指针,满指针是构造新数组的指针。更简单,但感觉上面我的比较好理解。
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
2023-7-8 二刷
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
//思路:单向单双指针
/*
slow 从当前位置走到右侧最近val处
fast 从当前位置走到右侧最近非val处
交换slow和fast的数据
!注意slow和fast的边界
*/
int slow = 0;
int fast = 0;
while(fast < nums.size())
{
while(slow < nums.size() && nums[slow] != val)slow++;
fast = slow;
if(fast < nums.size())
{
while(fast < nums.size() && nums[fast] == val)fast++;
if(fast < nums.size()) swap(nums[slow],nums[fast]);
}
}
return nums.size() - (fast-slow);
}
};
总结了一个规律:当外层while有边界判断,中间的++的每一个while循环判断中也要加上边界判断防止越界!
2024-4-11 三刷
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0; // 每次找到接下来一个==val的idx
int fast = 0; // 每次找到接下来第一个非val的idx
while(slow < nums.size() && fast < nums.size())
{
cout << slow << fast << endl;
while(slow < nums.size() && nums[slow] != val)slow++;
fast = slow + 1;
while(fast < nums.size() && nums[fast] == val)fast++;
if(slow < nums.size() && fast < nums.size())
{
int tmp = nums[slow];
nums[slow] = nums[fast];
nums[fast] = tmp;
}
}
return slow;
}
};
2.2 双指针拓展
26. 删除有序数组中的重复项 【这题要用上面的同向快慢双指针、双向的双指针哒咩!】
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
// 同向 快慢双指针做法
int slow = 0; // slow为构造新数组的指针
int old = -0xffff;
for(int fast = 0; fast < nums.size();fast++) //fast为寻找新数组的指针
{
if(nums[fast] != old)
{
old = nums[fast];
nums[slow++] = nums[fast];
}
}
return slow;
}
};
283. 移动零 比较简单 还是同向快慢双指针
class Solution {
public:
void moveZeroes(vector<int>& nums)
{
// 本题还是同向双指针
int slow = 0;
for(int fast = 0; fast < nums.size(); fast++)
if(nums[fast] != 0)nums[slow++] = nums[fast];
for(;slow < nums.size();slow++)
nums[slow] = 0;
}
};
还是同向双指针,我一开始没想到栈,没考虑,下面两类特殊情况,后面错的了才加flag打补丁,发现是栈,下面第一种情况需要分类讨论,靠一点分类逻辑:
// 我的版本
class Solution {
public:
bool backspaceCompare(string s, string t)
{
// 为了达到用 O(n) 的时间复杂度和 O(1) 的空间复杂度 还是要用同向双指针
int slow_s = 0;
int flag = 0; //当前要回退的flag=当前可以回退的个数 === 其实这里用栈比较好写
for(int fast_s = 0; fast_s < s.size();fast_s++)
{
if(s[fast_s] == '#' && flag) // 回退
{
slow_s--;
flag--;
}else
{
if(s[fast_s] == '#' && slow_s == 0)continue;
s[slow_s++] = s[fast_s];
flag++;
}
}
s.erase(slow_s,s.size() - slow_s);
flag = 0; //当前要回退的flag
int slow_t = 0;
for(int fast_t = 0; fast_t < t.size();fast_t++)
{
if(t[fast_t] == '#' && flag)
{
slow_t--;
flag--;
}
else
{
if(t[fast_t] == '#' && slow_t == 0)continue;
t[slow_t++] = t[fast_t];
flag++;
}
}
t.erase(slow_t,t.size() - slow_t);
cout << s << " " << t << endl;
if(s == t)return 1;
else return 0;
}
};
换一个写法——模拟栈
class Solution {
public:
bool backspaceCompare(string s, string t)
{
string a,b;
for(int i = 0; i < s.size();i++)
{
if(s[i] != '#')a.push_back(s[i]);
else if(s[i] == '#')
if(a.size() == 0)continue;
else a.pop_back();
}
for(int i = 0; i < t.size();i++)
{
if(t[i] != '#')b.push_back(t[i]);
else if(t[i] == '#')
if(b.size() == 0)continue;
else b.pop_back();
}
if(a == b)return 1;
else return 0;
}
};
感受
1. 复习熟练了二分和双指针
2. 写这些还是有趣的hh,靠一点推理( 但不多,可能简单题吧
3. 这几天忙编译code, SXL落下太多了,周六还在写周三day1的题目,累计用时大概3-4h( 断断续续写了几天
todo
1. 二分还剩下