leetcode 209 长度最小的子数组
一开始我想的方法和滑动窗口差不多,但是不知道怎么使用指针,最后用了暴力法去做(leetcode官方更新了用例之后暴力法已经无效了) 看了题解自己重新写了一遍移动窗口(其实也算是快慢指针的一种),涉及到子数组,子字符串的很多都用到移动窗口,下面逐行分析移动窗口
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int i=0;//窗口的头
int result=INT32_MAX; //INT32_MAX是一个宏,表示32位有符号整数的最大值,一般是 2^31 - 1
int sum=0;//窗口内数组的和
for(int j=0;j<nums.size();j++)//窗口的尾
{
sum+=nums[j];//如果sum<s则窗口尾不断后移直到大于等于
while(sum>=s)
{
int length=j-i+1;//获取窗口长度
result=result<length?result:length;//result用于存放符合条件的最小窗口长度
sum-=nums[i++];//窗口头后移,缩小窗口大小
}
}
return result==INT32_MAX ? 0 : result;;
}
};
整个过程把尾看作快指针,头看作慢指针,确定一个头后再移动找到这个头符合要求的最短长度,再换下一个头,此时长度必定是不够的所以尾继续后移,如此往复直到尾到达数组末
leetcode 59 螺旋矩阵2
这道题一开始想到了是画圈,但是没想明白怎么表示,代码写来写去都不行,我开始的思路时每走完一边left变量随着变化,但是这样造成了与其他变量比较时的困难
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>>matrix(n,vector<int>(n));
int num=1;int top=0,bottom=n-1,left=0,right=n-1;//设置左右边界和上下底
while(left <= right && top <= bottom)
{
for(int left_=left;left_<=right;left_++)//往右,直接填满
{
matrix[top][left_]=num;
num++;
}
for(int top_=top+1;top_<=bottom;top_++)//往下,直接填满
{//行填满因此top_加1
matrix[top_][right]=num;
num++;
}
if(left < right && top < bottom)//往左和往上是需要条件的,这是很重要的一个点,我没有观察到
{ for(int right_=right-1;right_>left;right_--)//往左,空一个
{//列填满因此right_减一
matrix[bottom][right_]=num;
num++;
}
for(int bottom_=bottom;bottom_>top;bottom_--)//往上,空一个
{//right_不等于left,因此bottom_等于bottom
matrix[bottom_][left]=num;
num++;
}
}//四个步骤执行完后,再统一对边界进行调整,逻辑更加清晰也避免比较的困难
left++;
right--;
top++;
bottom--;
}
return matrix;
}
};
将四个步骤抽象成一次,更要注意步骤执行的条件,前两个步骤是填满的而后两个不是
leetcode 19 删除链表的倒数第N个结点
第一时间想到的是两次遍历,后面看了题目要求的一次遍历我马上想到了滑动窗口,写好大致后改了一个bug马上通过了,时间超过100%
下面讲讲我那个bug,写好大体之后发现涉及头结点的有些用例过不了,发现有一个细节我没有分析好:窗口长度和倒数第n个的关系在涉及头结点和不涉及头结点时是不一样
窗口长度初始值是0,而倒数值n的初始值是1,判断条件应该是窗口长度length==n-1(涉及头结点)
错误范例:
1->2
若快指针移动到2,此时窗口长度length为1,当n=1时本应该删除2却删除了1因为慢指针还在1
错误原因是我把判断删除代码写在了移动指针之前,其实这并不算啥错误因为第一次while循环时必然不符合判断条件从而移动指针,所以本质上还是先移动再判断
正是因为先移动后判断所以最后一次判断时判断删除的条件是length==n(慢指针移动窗口值必然大于等于1,窗口长度初始值为1),慢指针指向倒数第n个数的前一位,直接操作慢指针即可
leetcode 160 相交链表
一开始想到了用哈希,一次遍历记录地址,一次遍历进行匹配
看了官方题解才明白怎么用双指针去做,官方的做法实在是太精妙
如果两链表等长,会同时遍历到相交结点;不等长,遍历a+b+c次也会到相交结点
如果等长且不相交,遍历到末尾就返回null;不等长,遍历n+m(a+b)次返回null;
还有一种更简洁的方法:使两链表剩余长度相同,再同时遍历
class Solution {
public:
int getLength(ListNode* head) {
int ans = 0;
for (;head;head = head->next) ++ans;
return ans;
}
ListNode *getIntersectionNode(ListNode* headA, ListNode* headB) {
int lenA = getLength(headA);
int lenB = getLength(headB);
int len = 0;
if (lenA > lenB) {
len = lenB;
while (lenA-- > lenB)
headA = headA->next;
} else {
len = lenA;
while (lenB-- > lenA)
headB = headB->next;
}
while (len--) {
if (headA == headB)
return headA;
headA = headA->next;
headB = headB->next;
}
return nullptr;
}
};
这个方法思想是把多余部分去掉,因为相交部分必然是两链表都有的,去掉部分自然不是相交的,这样可以用同时遍历确定相交节点
leetcode 142 环形链表2
这道题用哈希表很容易,但是双指针比较难想到,参考文章:代码随想录 (programmercarl.com)
设头结点到环入口距离为x,快慢结点相遇点到环入口距离为y,剩余路程为z
慢指针每次走一位,快指针每次走两位;因此快指针走的路程f是慢指针走的路程s的两倍:f=2s
因为快慢指针相对速度是1,因此两指针必然相遇且在环内相遇
因为快指针一定先进入环所以可以分为两种情况,快指针在环内走了1圈就相遇和走了n(n>1)圈再相遇,因此f=n(y+z)+x+y=2s;s=x+y;联立得x=(n-1)y+nz;也就是我们要求的入口位置
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (fast != nullptr) {
slow = slow->next;
if (fast->next == nullptr) {
return nullptr;
}
fast = fast->next->next;
if (fast == slow) {
ListNode *ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return nullptr;
}
};
当两指针重合时,设置一个从起点开始的ptr指针,循环直到找到慢指针,完成要求;
为什么是这样找呢?看x=(n-1)y+nz很容易理解,当n=1时,x=z,慢指针走到入口的路程刚好等于x
当n>1时,无非是上面那种情况再多走了n-1圈,还是走到了入口位置
这样分析拆解,再倒回来想代码即可
程序员面试金典 02.04 分割链表
这题感觉不是很难,但是思路欠缺了,记录一下
一开始想是在原链表上操作,分别找到第一个大于等于x和小于x的节点,设为大节点小节点,然后再用工作指针遍历遇到小于x的就接到小节点后面,移动小节点最后接到大节点,但是考虑不周有些情况满足不了把自己绕晕了,类体不好写
看了题解才想起用两个虚拟头结点,再结合上面的思路,只需一次遍历把值接到虚拟头节点后再合并两链表即可