704.二分查找
题目链接
(1)文字讲解:https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
(2)视频讲解:https://www.bilibili.com/video/BV1fA4y1o715
(3)题目链接:https://leetcode.cn/problems/binary-search/
有序数组+无重复元素,考虑二分
看到代码随想录之前的想法
二分只记得基本思路了,但是对于边界处理完全忘记了。主要困惑点在于:
- r到底等于length-1还是length?
- while中到底是<还是<=?
看到代码随想录之后的想法
有两种解法,但是思路是不变的,区间是不变量。
(1)如果是[a,b]
那么r=length-1,毕竟你要取到r,让nums[r]有意义。while就是<=,因为这里l=r是有可能的。
此外,每次迭代,l=m+1或者r=m-1,因为如下图:
这里可以得知,nums[m]在上一轮就check过了,没必要check一遍了,所以为了还是保持左闭右闭,每一轮迭代,l=m+1或者r=m-1。
(2)如果是[a,b)
那么r=length,毕竟取不到r。while就是<,因为这里l=r是不可能的。
此外,每次迭代,l=m+1或者r=m-1,因为如下图:
nums[m]究竟行不行,其实上一轮就已经check过了。我们还是想要保持左闭右开的空间,那么r就要故意取大1位,这样所有这一半需要被check的都囊括进下一轮中。
本题难点
想明白区间是不变量,推荐画图自己模拟一遍。
代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0;
int r = nums.size()-1;
while(l <= r){
//int m = l + (r - l)/2
int m = (l + r)/2;
if(nums[m] == target){
return m;
}else if(nums[m] < target){
l = m+1;
}else if(nums[m] > target){
r = m-1;
}
}
return -1;
}
};
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0;
int r = nums.size();
while(l < r){
//int m = l + (r - l)/2
int m = (l + r)>>1;
if(nums[m] == target){
return m;
}else if(nums[m] < target){
l = m+1;
}else if(nums[m] > target){
r = m;
}
}
return -1;
}
};
59.螺旋矩阵II
题目链接
(1)文字讲解:https://programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html
(2)视频讲解:https://www.bilibili.com/video/BV1SL4y1N7mV/
(3)题目链接:https://leetcode.cn/problems/spiral-matrix-ii/
看到代码随想录之前的想法
模拟题,但是我想的很复杂,控制边界条件很麻烦。
看到代码随想录之后的想法
最重要的是领悟“循环不变量”的思想。
1.绕圈,先分成一圈一圈绕,这样就有了loop这个变量,loop=n/2,记录绕多少圈。
2.一个圈有四个方向,按照题目的说法,一个正方形,先从上边开始从左到右,再从右边开始从上到下,再从下边开始从右到左,最后左边开始从下到上。每一圈都是从左上角开始,那么每一圈就需要更新左上角的坐标。于是有了starx,stary这两个变量,每一圈都++;
3.每一圈,每个方向,都设定成左闭右开,这样for循环的终止条件就是< n-offset,offset是每一次圈缩小的变量,每圈++。
4.在注意一下n如果是奇数,那么最中间的空格需要单独设定,于是有了middle这个变量。
本题难点
规定每一圈左闭右开,严格遵守循环不变量。
代码
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n,vector<int>(n,0));
int starx=0, stary=0;
int loop = n/2;
int middle = n/2;
int offest = 1;
int count = 1;
while(loop--){
int i = starx;
int j = stary;
for(; j < n-offest; j++){
res[starx][j] = count;
count++;
}
for(; i < n-offest; i++){
res[i][j] = count;
count++;
}
for(; j > stary; j--){
res[i][j] = count;
count++;
}
for(; i > starx; i--){
res[i][j] = count;
count++;
}
offest++;
starx++;
stary++;
}
if(n%2){
res[middle][middle] = count;
}
return res;
}
};
27.移除元素
题目链接
(1)文字讲解:https://programmercarl.com/0027.%E7%A7%BB%E9%99%A4%E5%85%83%E7%B4%A0.html
(2)视频讲解:https://www.bilibili.com/video/BV12A4y1Z7LP
(3)题目链接:https://leetcode.cn/problems/remove-element/
看到代码随想录之前的想法
暴力,开一个新数组然后复制和target不一样的。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
vector<int> res;
for(int i = 0; i < nums.size(); i++){
if(val != nums[i]){
res.push_back(nums[i]);
}else {
continue;
}
}
nums = res;
return res.size();
}
};
然后才看到不要另开数组,我就没办法了。
看到代码随想录之后的想法
双指针思想。慢指针用来记录需要被更新的target的位置,快指针寻找能够更新的后面的元素。一开始快慢指针同时出发,一旦碰到target,慢指针留下,快指针继续。
本题难点
一个双指针思想。还有一个其实是if(val != nums[fast])。这里我一开始也想到了双指针,但是我会让fast++,slow++,如果nums[i] == target,就只让fast++,nums[i] != target, nums[slow] = nums[target],slow++,fast++.
但是fast不管想不想等都要++,还不如直接让fast顶替i,如果不等于就让nums[slow] = nums[target] slow++,如果相等那就continue。总之这个代码在这个处理上很精巧,我想不到。
代码
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.size(); fast++){
if(val != nums[fast]){
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
977.有序数组的平方
题目链接
(1)文字讲解:https://programmercarl.com/0977.%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E7%9A%84%E5%B9%B3%E6%96%B9.html
(2)视频讲解:https://www.bilibili.com/video/BV1QB4y1D7ep
(3)题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/
看到代码随想录之前的想法
最直观的想法肯定是每个平方之后使用sort函数,但是要求使用O(n)的方法,这样就不行。
看到代码随想录之后的想法
双指针思想。其实如果数组有负数的话,最大的数只可能出现在数组的两边,中间的一定是最小的。所以可以设置两个指针,在数组两端,两个指针都往中间走。开一个新数组存平方后的结果。每次都比较指针的数的平方,哪个大就存在新数组里(倒序),然后往中间移动一位。直到两个指针相遇。
本题难点
最大的数只可能出现在两边,由此想到双指针思想。
代码
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(),0);
int k = nums.size()-1;
int i=0,j=nums.size()-1;
while(k>=0){
if(nums[i]*nums[i] >= nums[j]*nums[j]){
res[k] = nums[i]*nums[i];
i++;
}else{
res[k] = nums[j]*nums[j];
j--;
}
k--;
}
return res;
}
};
滑动窗口
以下题目都是滑动窗口,需要区分是最大滑动窗口和最小滑动窗口。最大的区别就是,最小滑动窗口跟随i更新,while的check条件是满足题目条件(也就是在while内更新),最大滑动窗口跟随j更新,while的check条件是不满足题目条件(也就是在while外更新),具体请看这个总结:
https://leetcode.cn/problems/fruit-into-baskets/solutions/1437444/shen-du-jie-xi-zhe-dao-ti-he-by-linzeyin-6crr/
209.长度最小的子数组
题目链接
(1)文字讲解:https://programmercarl.com/0209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.html
(2)视频讲解:https://www.bilibili.com/video/BV1tZ4y1q7XE
(3)题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/
看到代码随想录之前的想法
1.理解错题目意思了,以为不仅仅元素位置要是连续的,大小也必须是严格递增1或者递减1,所以添加了flag这个判断条件。res这个vector也根本没有存在的必要,不需要存找到的子序列,只需要存长度就行。
2.其实使用了双指针,我设置i就是为了指出子序列开始的位置,但是因为我少考虑了一种情况:我这里设置的其实是从i开始,一直到有满足条件的子序列出现,j结束,然后i=j,开始重复。但是,实际上这样忽略了一个问题,假设这么一个子序列:【2,3,4】,target=6,那么i=0,j=2的时候满足了,i=j,其实答案不是【2,3,4】,而是【3,4】,所以i应该慢慢+1,而不是直接跳到j的地方。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int flag = 0;//0 没有状态 1 递增序列 2递减序列
int sum = nums[0];
int len = 1, reslen = 100001;
vector<int> res;
res.push_back(nums[0]);
for(int i= 0, j = 1; j < nums.size(); j++){
if(nums[j] == nums[j-1] + 1){
if(flag == 0 || flag == 1){
flag = 1;
sum += nums[j]
res.push_back(nums[j]);
len++;
if(sum >= target){
if(len < reslen){
reslen = len;
}
}
res.clear();
res.push_back(nums[j]);
len = 1;
sum = nums[j];
flag = 0;
}else if(flag == 2){
if(sum >= target){
if(len < reslen){
reslen = len;
}
}
res.clear();
res.push_back(nums[j]);
len = 1;
sum = nums[j];
flag = 0;
}
}else if(nums[j] == nums[j-1] -1){
if(flag == 0 || flag == 2){
flag = 2;
sum += nums[j]
res.push_back(nums[j]);
len++;
}else if(flag == 1){
if(sum >= target){
if(len < reslen){
reslen = len;
}
}
res.clear();
res.push_back(nums[j]);
len = 1;
sum = nums[j];
flag = 0;
}
}else{
if(sum >= target){
if(len < reslen){
reslen = len;
}
}
res.clear();
res.push_back(nums[j]);
len = 1;
sum = nums[j];
flag = 0;
}
}
}
};
然后经过反思我写出了第二版,但是依然错了:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0;
int len = 100001;
for(int i=0,j=0; j < nums.size(); j++){
sum+=nums[j];
if(sum >= target){
while(i <= j){
if(sum >= target){
len = (j-i+1) < len? (j-i+1) : len;
}
sum -= nums[i];
i++;
}
}
}
return len == 100001? 0 : len;
}
};
这一版的问题其实和上一版差不多。这一版的想法是i先不动,j增加,直到substr的和已经大于target,j固定住,再来移动i,看看能不能把这个substr缩短。但是我的while条件是i<=j,也就是说,i一直要移动到j的位置上,但是忽略了一种情况就是,i可能移动到一半,substr的和就已经小于target了,如果这题的substr刚好出在此时的i到j+n这一段里面,那么就错了。如下图的案例:
总之,这样不行。
看到代码随想录之后的想法
滑动窗口的窗口里的条件一定是sum>=target,i,j在哪不重要。
本题难点
理解滑动窗口的判别条件。最小滑动窗口。
代码
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0;
int len = 100001;
for(int i=0,j=0; j < nums.size(); j++){
sum+=nums[j];
while(sum >= target){
len = (j-i+1) < len? (j-i+1) : len;
sum -= nums[i];
i++;
}
}
return len == 100001? 0 : len;
}
};
904. 水果成篮
题目链接
https://leetcode.cn/problems/fruit-into-baskets/description/
本题难点
最大滑动窗口。
代码
class Solution {
public:
int totalFruit(vector<int>& fruits) {
unordered_map<int,int> lz;
int len = 0;
for(int i = 0, j= 0; j < fruits.size(); j++){
lz[fruits[j]]++;
while(lz.size() > 2){
auto it = lz.find(fruits[i]);
it->second--;
if (it->second == 0) {
lz.erase(it);
}
i++;
}
len = len < (j-i+1) ? j-i+1 : len;
}
return len;
}
};
76. 最小覆盖子串
题目链接
https://leetcode.cn/problems/minimum-window-substring/
本题难点
最小滑动窗口
代码
class Solution {
public:
unordered_map<char,int> cnts,cntt;
bool check() {
for(auto it=cntt.begin();it!=cntt.end();it++){
auto its = cnts.find(it->first);
if(its != cnts.end()){
if(its->second < it->second){
return false;
}
}else{
return false;
}
}
return true;
}
string minWindow(string s, string t) {
int left = 0;
int len = 100001;
for(int k = 0; k < t.length(); k++){
cntt[t[k]]++;
}
for(int i = 0, j = 0; j < s.length(); j++){
cnts[s[j]]++;
while(check()){
if(len > j-i+1){
len = j-i+1;
left = i;
}
auto itt = cnts.find(s[i]);
itt->second--;
if(itt->second == 0){
cnts.erase(s[i]);
}
i++;
}
}
if(len != 100001)
{
return s.substr(left,len);
}else{
return "";
}
}
};
1004. 最大连续1的个数 III
题目链接
https://leetcode.cn/problems/max-consecutive-ones-iii/description/
本题难点
最大滑动窗口
代码
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int sum = 0;
int len = 0;
for(int i=0,j=0; j < nums.size(); j++){
if(nums[j] == 0){
sum++;
}
while(sum > k){
if(nums[i] == 0){
sum--;
}
i++;
}
len = len > (j-i+1)?len:j-i+1;
}
return len;
}
};
3. 无重复字符的最长子串
题目链接
https://leetcode.cn/problems/longest-substring-without-repeating-characters/
本题难点
最大滑动窗口
代码
class Solution {
public:
unordered_map<int,int> cnt;
bool check(){
for(auto it = cnt.begin(); it != cnt.end(); it++){
if(it->second >1){
return false;
}
}
return true;
}
int lengthOfLongestSubstring(string s) {
int len = 0;
for(int i =0, j =0; j < s.length(); j++){
cnt[s[j]]++;
while(!check()){
auto its = cnt.find(s[i]);
its->second--;
if(its->second == 0){
cnt.erase(s[i]);
}
i++;
}
len = len > (j-i+1)?len:(j-i+1);
}
return len;
}
};