CodeTop高频题目刷题笔记

CodeTop

如何使用markdown

3.无重复最长子串

力扣链接

题解

  • 模式识别1

    1.涉及出现次数,考虑散列表

    2.构造子串,散列表存其下标

  • 模式识别2

    1.涉及子串,考虑滑动窗口

滑动窗口基础解法(时间复杂度 O(n^2), 空间复杂度 O(1) )

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
      int start(0),end(0),curLen(0),maxLen(0);
        while (end<s.size()) {
            char cur_s=s[end];//更新当前位置的字符
            for (int i=start; i<end; i++) {
                //判断有无重复字符
                //有,更新窗口起点;没有则滑动窗口,增加窗口长度
                if (cur_s==s[i]) {   
                    start=i+1;               
                    curLen=end-start;//计算当前窗口长度
                    break;
                } 
            }   
            end++;//延长窗口  
            curLen++;//计算当前窗口长度
            maxLen=max(curLen,maxLen);//更新最长的值            
        }
    return maxLen;
    }    
};

优化解(Hashset,时间复杂度 O(n), 空间复杂度 O(n) )

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
      unordered_set<char> curSet;
      int begin(0),maxLen(0);
      for (int i=0; i<s.size(); i++) {
        while (curSet.count(s[i])) {
            curSet.erase(s[begin]);//找到,删除
            begin++;//重新设置起点
        }  
        //放入新的数据
        maxLen  = max(maxLen,i-begin+1); //比较当前窗口长度和历史长度
        curSet.insert(s[i]);//
      }
    return maxLen;
    }    
};

206.反转链表

力扣链接

题解

这道题看起来很简单,但是需要多做,不然容易阴沟里翻船

迭代解法

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == NULL || head->next == NULL) {
            return head;
        }//剪枝
        ListNode* front = nullptr;//双指针,同时front为虚拟头节点
        ListNode* cur = head;
        while (cur!=NULL) {
            ListNode* tep = cur->next;//保存链表后面的元素
            //保存完毕,可以操作cur改变其指向
            cur->next=front;//当前节点指向前面的节点
            front=cur;//换位置
            cur=tep;//恢复cur
        }
        return front;
    }
};

递归解法

刚开始接触递归可能会比较抽象,想要理解的还是要动手画画图,想清楚递归到最后的逻辑

class Solution {
public:
    int count=0;
    ListNode* reverseList(ListNode* head) {
        if (head == NULL || head->next == NULL) {
            return head;
        }
        ListNode *res;
        res=reverseList(head->next);
        //递归每退出一层,函数中 head 指针的指向都会发生改变,都指向上一个节点。
        //递归函数,考虑最底层逻辑,走到这里至少有两个节点,依次是head,res(即new_head),null
        head->next->next=head;//head 在往回退出 
        head->next=NULL;
        return res;//每次递归都返回新的头指针       
    }
};

46.LRU缓存

力扣链接

题解

LRU(Least Recently Used)也就是说我们认为最近使用过的数据应该是是「有用的」,很久都没用过的数据应该是无用的,内存满了就优先删那些很久没用过的数据。 由于需要O(1)的put()和get(),所以要想到哈希。哈希链表 LinkedHashMap,C++需要用双向链表去实现。由于需要手搓一个双向链表,时间久了很多细节容易忘记,只能当作默写题了。

// Node成员都是public的,推荐定义Node为struct
struct Node {
    int key;
    int val;
    Node *next, *prev;
    Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {}
};

class DoubleLinkedList {
    Node *head, *tail;
public:
    DoubleLinkedList() {
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head->next = tail;
        tail->prev = head;
    }

    void push_front(Node* node) {
        node->next = head->next;
        node->prev = head;
        node->next->prev = node;
        head->next = node;
    }

    void erase(Node* node) {
        node->next->prev = node->prev;
        node->prev->next = node->next;
    }

    Node* back() {
        return tail->prev;
    }

    void pop_back() {
        erase(tail->prev);
    }
};

class LRUCache {
public:
    int cap;
    unordered_map<int, Node*> key_node_map;
    DoubleLinkedList* node_list;
    LRUCache(int capacity) {
        cap = capacity;
        node_list = new DoubleLinkedList();
    }
    
    int get(int key) {
        // 不存在该key
        if (key_node_map.find(key) == key_node_map.end()) {
            return -1;
        } else {
            // 将当前节点在移动到双链表的首位
            Node* node = key_node_map[key];
            node_list->erase(node);
            node_list->push_front(node);
            return node->val;
        }
    }
    
    void put(int key, int value) {
         // 如果key存在,则修改对应的value并移动至链表首端
        if (key_node_map.find(key) != key_node_map.end()) {
            Node *node = key_node_map[key];
            node->val = value;
            node_list->erase(node);
            node_list->push_front(node);
        } else {
            // 如果缓存已满,则删除双链表末端节点
            if (key_node_map.size() == cap) {
                key_node_map.erase(node_list->back()->key);
                node_list->pop_back();
            }
            // 插入新节点
            Node *node = new Node(key, value);
            key_node_map[key] = node;
            node_list->push_front(node);
        }

    }
};
// 作者:ravenlite
// 链接:https://leetcode.cn/problems/lru-cache/solution/kan-lai-kan-qu-huan-shi-wo-de-zui-zhi-gu-cj9w/

纯哈希解法

当然,要是真的忘了或者不想造轮子,可以纯用哈希去做

class LRUCache {
//首先,要实现O(1)时间get只能使用hash表
public:
    LRUCache(int capacity) : capacity(capacity) {
        //直接使用capacity初始化capacity,(不懂的话搜:初始化成员列表)
        time = 0;
        poptime = 0;
    }
    
    int get(int key) {
        if(umap.find(key) != umap.end()){
            //如果找到了,我们更新使用时间
            int lasttime = times[key];
            times[key] = time;
            time_to_key.erase(lasttime);
            time_to_key[time] = key;
            time++;
            return umap[key];
        }else{
            return -1;
        }
    }
    
    void put(int key, int value) {
        //首先将其添加进新时间序列
        if(times.find(key) != times.end()){
            //如果之前就存在,我们要同时更新
            int lasttime = times[key];
            time_to_key.erase(lasttime);
            time_to_key[time] = key;
            times[key] = time;
            time++;
        }else{
            //如果之前不存在,则直接加入
            times[key] = time;
            time_to_key[time] = key;
            time++;
        }

        //然后将其插入
        umap[key] = value;

        //然后判断是否满员
        if(umap.size() == capacity + 1){
            //首先获取第一个有效的弹出时间节点
            while(time_to_key.find(poptime) == time_to_key.end()){
                poptime++;
            }
            //然后弹出它
            int theKey = time_to_key[poptime];
            times.erase(theKey);
            umap.erase(theKey);
            time_to_key.erase(poptime);
            poptime++;
        }
    }
private:
    int capacity;
    int time;                           //标记节点的插入序号
    int poptime;                        //标记最久未使用序号
    unordered_map<int,int> umap;        //记录<key,value>
    unordered_map<int,int> times;       //记录<key,插入时间>
    unordered_map<int,int> time_to_key; //记录<插入时间,key>
};
// 作者:wo-sun-ai-xue-xi
// 链接:https://leetcode.cn/problems/lru-cache/solution/146bu-shi-yong-lian-biao-shi-yong-san-ge-jz93/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

215. 数组中的第K个最大元素

力扣链接

题解

由于库函数的解是O(nlogn)的,所以不能一句话就糊弄过去,比如:

return sort(nums.begin(), nums.end(), greater<int>()), nums[k - 1];

要是下面的解法实在没写出来,这句话可以多提交几次,保证AC。

优先级队列

当k较小的时候,期望的时间复杂度是趋近O(n)的,严格来说其复杂度为O(nlog(k))

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<>> q;
        for (const auto &num: nums) {
            if (q.size() < k) {
                q.push(num);
            } else if (q.top() < num) {
                q.pop();
                q.push(num);
            }
        }
        return q.top();
    }
};
// 作者:XiaoCaiGang
// 链接:https://leetcode.cn/problems/kth-largest-element-in-an-array/solution/you-xian-dui-lie-by-xiaocaigang-arcc/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

堆排

手搓堆排解法,先把数组放进一个完全二叉树 [记一个二叉树的深度为ℎ,若除第ℎ层外,该二叉树的其它各层[1,ℎ−1]的节点数均达到最大值(满的),且第ℎ层所有的节点均连续集中在最左边,那么这棵树即为一棵完全二叉树.]

class Solution 
{
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        int n = nums.size();
        build_maxHeap(nums);
        for (int i = 0; i < k - 1; i ++)
        {
            swap(nums[0], nums[n-1-i]);
            adjust_down(nums, 0, n-1-i - 1);
        }
        return nums[0];
    }


    void build_maxHeap(vector<int> & nums)
    {
        int n = nums.size();
        for (int root = n/2; root > -1; root --)
            adjust_down(nums, root, n - 1);
    }

    void adjust_down(vector<int> & nums, int root, int hi)
    {
        if (root > hi)
            return ;
        int t = nums[root];
        int child = 2 * root + 1;
        while (child <= hi)
        {
            if (child + 1 <= hi && nums[child] < nums[child + 1])
                child ++;
            if (t > nums[child])
                break;
            nums[root] = nums[child];
            root = child;
            child = 2 * root + 1;
        }
        nums[root] = t;
    }
};
// 作者:XingHe_XingHe
// 链接:https://leetcode.cn/problems/kth-largest-element-in-an-array/solution/cpython3java-1da-gen-dui-diao-ku-2shou-l-xveq/

25. K 个一组翻转链表

力扣链接

题解

栈的解法

笔试的时候只是想AC的话用栈容易想一点(短时间内,想做出这题要基础很好),想要O(1)空间解决要画图,把逻辑理清楚,不过我懒得做了。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        stack<ListNode*> stk;
        ListNode* dumphead = new ListNode(0,head);//记录一下结果的头指针
        ListNode* pre = dumphead;//活动的指针pre
        ListNode* cur = head;
        //可以先算算有几轮,当然也可以写while循环,我怎么写是为了容易看懂
        int count = 0, group = 0;
        while (cur!=nullptr) {
            cur=cur->next;
            count++;
        }
        group = count/k;
        //cout <<"group="<< group << endl;
        cur=head;//恢复

        for (int i=0; i<group; i++) {
            //每一轮的操作
            for (int i=0; i<k; i++) {
                stk.emplace(cur);
                cout<<cur->val<<"   "<<"入栈"<<"    ";
                cur=cur->next;       
            }//进栈
            cout<<endl;
            for (int i=0; i<k; ++i) {
                pre->next=stk.top();
                stk.pop();
                cout<<"stk.size="<<stk.size()<<" ";
                pre=pre->next;//指针右移
                cout<<"pre->val="<<pre->val<<endl;
            }//出栈           
        }  
        pre->next=cur;//连接上后面的,不需要反转的   
        return dumphead->next;
    }
};

本文章使用limfx的vscode插件快速发布