leetcode每日一题复盘(数组,链表)

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的就接到小节点后面,移动小节点最后接到大节点,但是考虑不周有些情况满足不了把自己绕晕了,类体不好写

看了题解才想起用两个虚拟头结点,再结合上面的思路,只需一次遍历把值接到虚拟头节点后再合并两链表即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值