Bootstrap

秋招突击——7/8——复习{快速排序模版题——数组中第K大的元素、LRU缓存的实现}——新作{单调栈模版题——每日温度}

引言

  • 今天又是新的一周了,今天继续加油,起的挺早的,背完书睡了一会,整的有点懵,先把今天的算法题做了!
  • 不知道今天字节一面,能不能通过,不过据说好难呀,字节是大厂里面最难面试的!
  • 看了一下他的内容,感觉要求还是很高的,重点要求高可用高并发,尤其是消息队列那一部分,什么kafka这些暂时还不会,权当接受鞭笞了吧!
  • 一紧张,一个上午啥也不想干,就想停在这里,这样可不行呀!

复习

快速排序模版题——数组中第K大的元素

第一次做题的链接

回顾实现
  • 规定了时间复杂度是O(n),所以这里是基于快排实现的,快排的时间复杂度是O(nlogn),但是这里每次都取一半,最终的结果逼近2n,就是O(n)时间复杂度,这里直接使用快排的模板去做。
  • 实现如下
    模板记录
  • 这里的模板写错了,应该是以j作为分割线,因为最终肯定是j移动到中间的
  • 进行交换的之后,要判定i和j之间的相对关系
class Solution {
public:
    int quicksort(vector<int> &nums,int l,int r,int k){
        if(l >= r)   return nums[k];
        int i - l - 1 ,j = r + 1 ,x =(l + r) >>1;
        while(i < j){
            do i++;while(nums[i] < nums[x]);
            do j --;while(nums[j] > nums[x]);
            swap(nums[i],nums[j]);
        }
        quicksort(nums,l,x - 1,k),quicksort(nums,x + 1,r,k);
    }
    int findKthLargest(vector<int>& nums, int k) {
        return quicksort(nums,0,nums.size() - 1,k);
    }
};


void quick_sort(int q[],int l,int r){
	if(l >= r)	return ;
	// 确定中间值、左边界、右边界
	// 中间元素不参加排序,i是从x的左侧一个开始,j是从x的右侧开始
	int i = l - 1,j = r + 1,x = q[l + r >> 1];
	while(i < j){
		do i ++; while(q[i] < x);
		do j -- ;while(q[j] > x);
		if(i < j)	swap(q[i],q[j]);
	}
	quick_sort(q,l,j),quick_sort(q,j + 1,r);
}

这个模板有几个特征需要注意

  • 如果数量是奇数的话,最终j就是第k个已经排序好的元素
  • 如果数量是偶数的话,左右边界会互换,然后左右边界并不一定是已经排序好的元素,所以要始终进行重排序

注意,是第k大的元素,就是要从大到小进行排序,这里搞错了,搞反了!!

class Solution {
public:
    int quicksort(vector<int> &nums,int l,int r,int k){
        if(l == r)   return nums[k];
        int i = l - 1 ,j = r + 1 ,x =nums[(l + r) >>1];
        while(i < j){
            do i++;while(nums[i] > x);
            do j --;while(nums[j] < x);
            if (i < j) swap(nums[i],nums[j]);
        }
        if(i == j && j == k)  return nums[k];
        if(j < k)   return quicksort(nums,j + 1,r,k);
        else    return quicksort(nums,l,j,k);
    }
    int findKthLargest(vector<int>& nums, int k) {
        return quicksort(nums,0,nums.size() - 1,k - 1);
    }
};

LRU缓存的实现

在这里插入图片描述
在这里插入图片描述
注意

  • 一个人是如何在一个地方跌倒了两次的,命运总是如此巧合,在腾讯的时候就面试过这道题,然后在字节的时候又来了,太真实了!兄弟!
  • 没必要了,这道题你怎么都得背下来,不仅仅是背下来,你还得在clion中再写一遍!从头写一遍!
个人实现
  • 今天手撕部分主要有以下两个缺点
    • leetcode官网用习惯了,连类的构造函数都不会写了,正常手撕代码题,并不会像leetcode一样,给你打好一个框架
    • 我的命名方式太差了,居然和平常些算法题一样,怎么简单怎么来了,完全没有意义!不要省这个时间,然后键盘好好用用!老师打错字,打慢点没事的!

下面是我个人实现

  • 上一次没有好好做,然后这一次做了才发现一个问题,就是没有办法好好确认最后一个节点的指针的坐标,所以就没有办法进行节点清除
  • 很多地方,写的都是不对的,有很大的问题,并没有判定是否为空!不然就是非法内存访问了!
class LRUCache {
public:
struct Node{
    int value;
    Node* pre;
    Node* next;
    Node(int v):value(v),pre(nullptr),next(nullptr){};
};

    int capacity;
    int cur_capacity;
    unordered_map<int,Node*>  dict;
    Node* dummy;
    Node* tail;

    LRUCache(int capacity){
        this->capacity = capacity;
        this->cur_capacity = 0;
        this->dummy = new Node(-1);
        this->tail = dummy;
    }

    void moveFirst(Node* node_move){

        // remove the node_move in the list
        if(node_move->next) node_move->next->pre = node_move->pre;
        node_move->pre->next = node_move->next;

        // move the node_move to first
        node_move->pre = dummy;
        dummy->next = node_move;
        Node* temp = dummy->next;
        if(temp){
            node_move->next = temp;
            temp->pre = node_move;
        }
    }

    int get(int key){
        // analysis the key exists
        if(dict.count(key) == 0) return -1;
        // move the Node to the first
        moveFirst(dict[key]);
        return dict[key]->value;
    }

    void put(int key,int value){
        // judge the whether the key exists
        if(dict.count(key) == 0){
            // the key does not exists
            dict[key] = new Node(value);
            if(cur_capacity == 0) tail = dict[key];

            // the capacity is not full
            if(cur_capacity < capacity) {
                dict[key]->next = dummy->next;
                dict[key]->pre = dummy;
                dummy->next = dict[key];
                if(dummy->next) dummy->next->pre = dict[key];

                cur_capacity ++;

            }else{
                // the cache is full ,remove the tail node;
                tail = tail->pre;
                delete tail->next;
                
            }

        }else{
            dict[key]->value = value;
            moveFirst(dict[key]);
        }
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

总结

  • 这里写的真的是糟糕,有几个地方是有问题的,具体以下几个部分
    • 指针那里的操作还是有很大的问题,不是不理解,是感觉自己不规范,每次都忘记判定下一个next指针是否为空,然后做了很多不必要的判断
    • 第二个就是,命名也没有很规范
参考实现
  • 这里的思路基本上是一致的,主要是看看他怎么实现删除最后一个节点的,以及如何维护不同指针的!
#include <iostream>
#include <unordered_map>
using namespace std;



class LRUCache{
public:
    struct Node{
//        int value;   多个变量,同类型,同行进行命名
        int key,val;
        Node *left,*right;  // 指针的命名这里也是有问题的,就是
//        Node* right;
//        Node(int v):value(v),pre(nullptr),next(nullptr){};
        Node(int _key,int _val):key(_key),val(_val),left(NULL),right(NULL){};
    } *L ,*R;
    unordered_map<int,Node*>  dict;
    int num;// 这个cache的容量大小



    LRUCache(int capacity){
        num = capacity;
        // 同时建立头节点和尾节点,这样能够防止空指针,比我的高明多了
        L = new Node(-1,-1),R = new Node(-1,-1);
        L->right = R,R->left = L;
    }

    void insert(Node *p){
        // insert new node to the first
        // assign the left and right point of the node
        p->right = L->right;
        p->left = L;
        // assign the left the node
        L->right->left = p;
        L->right = p;
    }

    void remove(Node *p){
        // remove the final node
        p->right->left = p->left;
        p->left->right = p->right;
    }


    int get(int key){
        // analysis the key exists
        if(dict.count(key) == 0) return -1;
        // remove the node and insert the node again
        auto p = dict[key];
        remove(p);
        insert(p);
        return p->val;
    }

    void put(int key,int value){
        // judge the whether the key exists
        if(dict.count(key) == 0){
           // judge the size of cache by the dict
           if(dict.size() == num){
               // the cache is full
               auto p = R->left;
               remove(p);
               dict.erase(key);
               delete p;
           }
           // insert new node to cache
           auto p = new Node(key,value);
           insert(p);
           dict[key] = p;

        }else{
            auto p = dict[key];
            p->val = value;
            // refresh the node access time
            remove(p);
            insert(p);
        }
    }
};

int main(){
    LRUCache lru(2) ;
    lru.put(1,3);
    lru.put(1,4);
    lru.put(1,5);
    cout<<lru.get(1)<<endl;
    lru.put(4,5);
    cout<<lru.get(3)<<endl;
    return 0;

}

总结

  • 下面就上面几个问题进行逐个回答,解释一下

  • 为了防止出现空指针的判定

    • 这里创建了一个头节点和尾节点,我在面试的时候想到了,但是觉得可能会浪费空间,所以就想多写一些逻辑进行判定,但是效果并不好的。
  • 通过字典来判定当前的cache的数量

    • 我在面试的时候是通过的一个新的变量n来维护对应的cache长度,但是这里完全没有必要,只需要通过字典进行维护即可,因为字典保存的是有效的字典空间
    • 字典的大小 小于阈值,就不需要判定是否需要删除最后一个元素,只有达到阈值,才需要删除目标元素!

即使增加了最大生存时间,也不需要单独额外判定,只需要在获取元素的时候判定一下即可,因为没有必要删除过期的元素!!

  • 获取当前系统运行时间相关操作
  • ctime包的clock_t对象,使用clock()函数获取对应的时间戳
  • 使用double来保存时间的长度
#include <iostream>
#include <ctime>

using namespace std;

int main() {
    // 获取程序开始时间
    clock_t start = clock();

    // 模拟一些操作
    for (volatile int i = 0; i < 100000000; ++i);

    // 获取程序结束时间
    clock_t end = clock();

    // 计算运行时间
    double duration = double(end - start) / CLOCKS_PER_SEC;
    cout << "Program run time: " << duration << " seconds" << endl;

    return 0;
}

增加了过期时间的相关操作

#include <iostream>
#include <unordered_map>
#include <ctime>
using namespace std;



class LRUCache{
public:
    struct Node{
//        int value;   多个变量,同类型,同行进行命名
        int key,val;
        Node *left,*right;  // 指针的命名这里也是有问题的,就是
        clock_t useTime;
//        Node* right;
//        Node(int v):value(v),pre(nullptr),next(nullptr){};
        Node(int _key,int _val):key(_key),val(_val),left(NULL),right(NULL),useTime(0){};
    } *L ,*R;
    unordered_map<int,Node*>  dict;
    int num;// 这个cache的容量大小
    double duraTime;



    LRUCache(int capacity,double duration){
        num = capacity;
        duraTime = duration; // assign the life of the node
        // 同时建立头节点和尾节点,这样能够防止空指针,比我的高明多了
        L = new Node(-1,-1),R = new Node(-1,-1);
        L->right = R,R->left = L;
    }

    void insert(Node *p){
        // insert new node to the first
        // assign the left and right point of the node
        p->right = L->right;
        p->left = L;
        // assign the left the node
        L->right->left = p;
        L->right = p;
        // refresh the StartTiem
        p->useTime = clock();
    }

    void remove(Node *p){
        // remove the final node
        p->right->left = p->left;
        p->left->right = p->right;
    }


    int get(int key){
        // analysis the key exists
        if(dict.count(key) == 0) return -1;
        auto p = dict[key];
        p->useTime = clock();
        remove(p);

        // judge whether the node exists
        if((clock() - p->useTime) > duraTime)  {
            delete p;
            dict.erase(key);
            return -1;
        }
        // remove the node and insert the node again
        insert(p);
        return p->val;
    }

    void put(int key,int value){
        // judge the whether the key exists
        if(dict.count(key) == 0){
           // judge the size of cache by the dict
           if(dict.size() == num){
               // the cache is full
               auto p = R->left;
               remove(p);
               dict.erase(key);
               delete p;
           }
           // insert new node to cache
           auto p = new Node(key,value);
           p->useTime = clock();
           insert(p);
           dict[key] = p;

        }else{
            auto p = dict[key];
            p->val = value;
            p->useTime = clock();
            // refresh the node access time
            remove(p);
            insert(p);
        }
    }
};

int main(){
    LRUCache lru(2,2000000.00) ;
    lru.put(1,3);
    lru.put(1,4);
    lru.put(1,5);
    cout<<lru.get(1)<<endl;
    lru.put(4,5);
    cout<<lru.get(3)<<endl;
    return 0;

}

下面是Java版本的

import java.util.*;

class LRUCache {
    class Node {
        int key, val;
        Node left, right;
        long useTime;

        Node(int _key, int _val) {
            key = _key;
            val = _val;
            left = null;
            right = null;
            useTime = 0;
        }
    }

    private Node L, R;
    private Map<Integer, Node> dict;
    private int num;
    private double duraTime;

    public LRUCache(int capacity, double duration) {
        num = capacity;
        duraTime = duration; // assign the life of the node
        // 同时建立头节点和尾节点,这样能够防止空指针,比我的高明多了
        L = new Node(-1, -1);
        R = new Node(-1, -1);
        L.right = R;
        R.left = L;
        dict = new HashMap<>();
    }

    private void insert(Node p) {
        // insert new node to the first
        // assign the left and right point of the node
        p.right = L.right;
        p.left = L;
        // assign the left the node
        L.right.left = p;
        L.right = p;
        // refresh the StartTiem
        p.useTime = System.currentTimeMillis();
    }

    private void remove(Node p) {
        // remove the final node
        p.right.left = p.left;
        p.left.right = p.right;
    }

    public int get(int key) {
        // analysis the key exists
        if (!dict.containsKey(key)) return -1;
        Node p = dict.get(key);
        p.useTime = System.currentTimeMillis();
        remove(p);

        // judge whether the node exists
        if ((System.currentTimeMillis() - p.useTime) > duraTime) {
            dict.remove(key);
            return -1;
        }
        // remove the node and insert the node again
        insert(p);
        return p.val;
    }

    public void put(int key, int value) {
        // judge the whether the key exists
        if (!dict.containsKey(key)) {
            // judge the size of cache by the dict
            if (dict.size() == num) {
                // the cache is full
                Node p = R.left;
                remove(p);
                dict.remove(p.key);
            }
            // insert new node to cache
            Node p = new Node(key, value);
            p.useTime = System.currentTimeMillis();
            insert(p);
            dict.put(key, p);

        } else {
            Node p = dict.get(key);
            p.val = value;
            p.useTime = System.currentTimeMillis();
            // refresh the node access time
            remove(p);
            insert(p);
        }
    }

    public static void main(String[] args) {
        LRUCache lru = new LRUCache(2, 2000000.00);
        lru.put(1, 3);
        lru.put(1, 4);
        lru.put(1, 5);
        System.out.println(lru.get(1)); // 应该打印 5
        lru.put(4, 5);
        System.out.println(lru.get(3)); // 应该打印 -1 因为 key 3 不存在
    }
}

新作

栈——每日温度

题目链接

在这里插入图片描述
注意

  • 返回的是系列中第一个会单调递增的元素的索引,如果是零,后续没有比他大的元素了
  • 数组长度的是 1 0 5 10^5 105,这里有可能会超时
  • 温度的区间是30到100,并不会出现越界
个人实现
  • 这里的思路就很明确了,感觉类似单调递增队列,不过数据长度没有限制,想想看怎么实现哈!
  • 这里算是给了一点提示吧,因为这个章节就是和栈相关的,所以这里使用栈来实现,具体思路如下
    在这里插入图片描述
  • 使用一个保存索引的队列来维护
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temp) {
        int m = temp.size();
        vector<int> f(m );
        stack<int> upst;
        upst.push(0);
        for(int i = 1;i < temp.size();i ++){
            // compare the top elements
            while(!upst.empty() && temp[i] > temp[upst.top()]){
                f[upst.top()] = i - upst.top();
                upst.pop();
            }
            upst.push(i);
        }
        return f;
    }
};

在这里插入图片描述
总结

  • 哇靠,难得那么顺,可能这题比较简单?不过好歹是中等题!还能提前做完,真舒服!
参考实现
  • 他说这道题就是单调栈的模版题,然后花了一分半写了单调栈的模板,然后改了几个参数,就结束了。
  • 这里好好看看他给的模版吧!
class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temp) {
        int m = temp.size();
        vector<int> f(m);
        stack<int> upst;
        for(int i = temp.size() - 1;i >= 0;i --){
            while(!upst.empty() && temp[i] >= temp[upst.top()]) upst.pop();
            if(upst.size())  f[i] = upst.top() - i;
            upst.push(i);
        }
       
        return f;
    }
};

内存效率确实更高了
在这里插入图片描述
总结

  • 但是我觉得这个题目比较难记,虽然和我的思路大致是相同的,但是想起来总有点怪怪的,想想看怎么记下来会更好记,脑袋不好用,记不住事情。
  • 右边第一个大于我的元素,左大弹(左边元素大,弹出栈),答栈顶(答案就是栈顶元素)
  • 能记就记,记不住就用我们自己的方法
  • 说到模版题了,再去回忆一个模板题,基于快速排序实现的。

总结

  • 以后的所有代码都要在clion中实现一下,因为手撕代码要求你能从头开始运行一个程序,是从零开始的!所以,你写习惯了idle没意义!
  • 今天面试对我来说,获益匪浅,字节的老师真的很棒,我觉得虽然过不了,但是让我成长了不少
    • 后续所有的算法题,都要使用java写一遍,
    • 写一个完整的对象,注意自己的命名细节
    • 重视自己的编程习惯
    • 不要紧张,有想法赶快实现,总共就20分钟,你想那么多干嘛

明天抽时间,好好再过一遍面试的题目,进一步加深我们的面试题目的回答深度

  • 心态还是不够好,前面的八股说的七零八落的,后面的手撕就紧张了,很多东西都没有想明白,就开始落手了,其实练了那么久,算法能力并没有那么差。
  • 我算是知道为什么自己会记得那么乱了,两个语言生命对象的方式不同,我老是记混!明天加油!
;