图解算法数据结构刷题笔记02

本文解析了剑指Offer中的11道经典算法题,包括替换空格、从尾到头打印链表、用两个栈实现队列等,提供详细的解题思路与代码实现。

系列文章目录

图解算法数据结构刷题笔记01



前言

刷题来源:https://leetcode.cn/leetbook/detail/illustration-of-algorithm/
主要目的:

  • 复习数据结构与算法的核心知识点
  • 掌握互联网笔面试的主要算法题型,能过面试算法题。

内容概述:
在这里插入图片描述


1、剑指 Offer 05. 替换空格

战绩
在这里插入图片描述
思路

面试前先询问是否可以修改原字符串,可以就解法一:原地修改,不可以就解法二:遍历添加

代码
解法一:原地修改

class Solution {
public:
    string replaceSpace(string s) {
        for(int i=0;i<s.size();++i){
            if(s[i] == ' '){
                s.replace(i,1,"%20");
            }
        }
        return s;
    }
};

解法二:遍历添加

class Solution {
public:
    string replaceSpace(string s) {
        int size = s.size();
        string ans;
        for(int i=0; i < size; ++i){
            if (s[i] == ' ') {
                ans += "%20";
            } else {
                ans += s[i];
            }
        }
        return ans;
    }
};

2、剑指 Offer 06. 从尾到头打印链表

战绩
在这里插入图片描述

思路

先遍历一遍链表得到链表长度,根据链表长度构建结果数组,再遍历一遍链表,向结果数组从尾到头进行插入
注意结果数组边界不要溢出了!

代码

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        int count = 0;
        ListNode* cur = head;
        while(cur != NULL){
            count++;
            cur = cur->next;
        }
        cur = head;
        vector<int> ans(count);
        for(int i=count-1;i>=0;--i){
            ans[i] = cur->val;
            cur = cur->next;
        }
        return ans;
    }
};

3、剑指 Offer 09. 用两个栈实现队列

战绩
在这里插入图片描述
思路

队列的入队,辅助栈来数据就压入栈即可
队列的出队,
如果主栈和辅助栈都为空返回-1,
如果主栈为空,辅助栈非空,就把辅助栈中的元素全部依次压入主栈中并返回栈顶元素,
如果主栈非空,默认返回栈顶元素即可

代码

class CQueue {
public:
    stack<int> a,b;//a辅助栈、b主栈
    CQueue() {
    }
    
    void appendTail(int value) {
        a.push(value);
    }
    
    int deleteHead() {
        if(b.empty() && a.empty())  return -1;
        else if( b.empty() && !a.empty() ) {
            while(!a.empty()){
                b.push(a.top());
                a.pop();
            }
        }
        int tmp = b.top();
        b.pop();
        return tmp;
    }
};

4、剑指 Offer 20. 表示数值的字符串

战绩
在这里插入图片描述

思路

这题之前特地发过一篇文章:剑指offer20题 表示数值的字符串 这题实在是太优雅了
记好三条规律和一个特别情况就能快速敲出

代码

class Solution {
public:
    static void trim(string &s){
        if( !s.empty() ){
            s.erase(0,s.find_first_not_of(" "));
            s.erase(s.find_last_not_of(" ") + 1);
        }
    }
    bool isNumber(string s) {
        if (s.empty() || s.size() == 0) return false;
        trim(s);
        bool is_num = false;//判断数字是否出现过
        bool is_dot = false;//判断.是否出现过
        bool is_eorE = false;//判断e或者E是否出现过
        int i = 0,size = s.size();
        for (; i < size; i++) {
            //判定为数字,则标记is_num
            if (s[i] >= '0' && s[i] <= '9') {   
                is_num = true;
            //判定为.  需要前面没出现过.并且没出现过e
            } else if (s[i] == '.' && !is_dot && !is_eorE) {  
                is_dot = true;       
            //判定为e或者E,需要前面没出现过e,并且出现过数字
            } else if ((s[i]=='e' || s[i]=='E') && !is_eorE && is_num) {
                is_eorE = true;
                is_num = false;//为了避免123e这种请求,出现e之后就标志为false      
            //判定为+-符号,只能出现在第一位或者紧接e后面
            } else if (s[i] == '-' || s[i] == '+'){
                if(i!=0 && s[i-1] != 'e' && s[i-1] != 'E'){
                    return false;
                }
            } else {
                return false;
            }
        }
        return is_num;
    }
};

5、剑指 Offer 24. 反转链表

战绩
在这里插入图片描述

思路

用cur指向要反转的节点。pre指向反转节点的上一个节点,初始为空。
只要cur不为空就继续遍历下一个节点,循环执行
先用tmp保存下一个节点免得找不到了,然后通过两指针操作反转节点

代码

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *cur=head,*pre=nullptr;
        while(cur!=nullptr){
            ListNode *tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

6、剑指 Offer 30. 包含 min 函数的栈

战绩
在这里插入图片描述

思路

这题跟用两个栈实现队列有点像,都是用两个栈来实现的,所以先直接定义主要栈和辅助栈
1、入栈时,先直接压入主要栈,如果比辅助栈顶元素小或者等于就压入辅助栈(注意辅助栈最为空的情况下不管大小都是要压入的,我在这里就踩了坑,检查好久才改正)
2、出栈时,先直接出主要栈的元素,如果和辅助栈相等就也出了
3、查看栈顶元素时,直接返回主要栈栈顶元素即可
4、查看当前栈最小元素时,直接返回辅助栈栈顶元素即可
总的来说,代码很简单,主要要理解辅助栈的作用,特别是入栈时的操作一定要注意!

代码

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> sta,stb;//sta主要栈,stb辅助最小栈
    MinStack() {
    }
    
    void push(int x) {
        sta.push(x);
        if(stb.empty() || x <= stb.top()){
            stb.push(x);
        }
    }
    
    void pop() {
        if(sta.top() == stb.top()){
            stb.pop();
        }
        sta.pop();
    }
    
    int top() {
        return sta.top();
    }
    
    int min() {
        return stb.top();
    }
};

7、剑指 Offer 35. 复杂链表的复制

战绩

ps:这里之后就不放战绩图了,没啥用,专注解题思路和代码吧!

思路

这题第二次写只记得复制拆分了,具体的咋弄完全忘了,所以狼狈的看了一遍,又自己敲然后又敲错了,比对之后才更正,不多bb了。
三步走即可:
1、复制链表节点插入原节点和原节点后一个节点之间;
2、复制链表节点的随机节点
3、链表拆分
最关键的链表拆分踩坑就是,最后一个原节点本来是指向NULL的,但是复制之后就指向了和自己相同的复制节点,循环拆完后,要手动把最后一个节点指向空;

代码

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == NULL) return NULL;

        //1、复制链表节点
        Node* cur = head;
        while(cur != NULL){
            Node* tmp = new Node(cur->val);
            tmp->next = cur->next; 
            cur->next = tmp;
            cur = tmp->next;
        }
        
        //2、构建各新节点的random
        cur = head;
        while(cur != NULL){
            if(cur->random != nullptr)
                cur->next->random = cur->random->next;
            cur = cur->next->next;
        }

        //3、拆分链表
        cur = head;               
        Node* newhead = head->next;
        Node* newcur = head->next;
        while(newcur->next != NULL){
            cur->next = newcur->next;
            cur = newcur->next;
            newcur->next = cur->next;
            newcur = cur->next;
        } 
        cur->next = nullptr;

        return newhead;
    }
};

8、剑指 Offer 58 - II. 左旋转字符串

思路

这题太简单就遍历拼接就行了,但是也有比较秀的操作,利用C艹语法特性三次翻转解题

代码

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        reverse(s.begin(), s.begin() + n);
        reverse(s.begin() + n, s.end());
        reverse(s.begin(), s.end());
        return s;
    }
};

9、剑指 Offer 59 - I. 滑动窗口的最大值

思路
这题个人感觉是这组题目里面最难的一题,直接模拟的话就是定义左右边界分别为【i,j】,每往右一步就遍历一遍窗口里的最大值出来加入到结果数组中,这样时间复制度很高而且不符合题目要求.
所以,引入单调队列deque存储窗口内的元素,每轮窗口滑动移除了元素 nums[i - 1]nums[i−1] ,需将 dequedeque 内的对应元素一起删除。
每轮窗口滑动添加了元素 nums[j + 1] ,需将 deque 内所有 小于 nums[j + 1] 的元素删除。
这样一来,每次滑动后,单调队列的队首元素就是最大的值,用一个数组存起来即可。
在这里插入图片描述

代码

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;//存放结果
        if (nums.size()==0 || k==0) return res;
        deque<int> d;//单调队列
        for (int j=0, i=1-k; j<nums.size(); i++, j++) {//i左边界、j右边界
            if (i>0 && d.front()==nums[i-1]) 
                d.pop_front();
            while (!d.empty() && d.back() < nums[j]) 
                d.pop_back();
            d.push_back(nums[j]);//新元素入队列
            if (i>=0) 
                res.push_back(d.front());
        }
        return res;
    }
};

10、剑指 Offer 59 - II. 队列的最大值

思路
队列queue么实现函数返回队列的最大值,所以,来个deque作为辅助队列存储队列的历史最大值
入队时,直接入队列a,如果辅助队列b非空并且b队列中比入队的值要小的值都出队,最后把改值加入辅助队列b
出队时,如果a为空返回-1,如果a非空且出队的值和b顶端的相同,那么b也出队,否则默认返回a队列的顶端的值即可。
max_value就是辅助队列b的front()

代码

class MaxQueue {
public:
    deque<int> b;//单调队列
    queue<int> a;//队列,存储
    MaxQueue() {
    }
    
    int max_value() {
        return b.empty() ? -1 : b.front();
    }
    
    void push_back(int value) {
        a.push(value);
        while(!b.empty() && b.back() <= value)
            b.pop_back();
        b.push_back(value);
    }
    
    int pop_front() {
        if (a.empty()) return -1;
        if (a.front() == b.front()) b.pop_front();
        int tmp = a.front();
        a.pop();
        return tmp;
    }
};

11、剑指 Offer 67. 把字符串转换成整数

思路
这题也是困扰我好久,解题方法也很简单就是先处理空格和±符号,然后遍历转整数即可。
主要是INT类型的溢出问题,收获颇多,k神的处理方法真的是太秀了,就直接除2,这样就能进行运算而且不怕溢出了,我开始想用long来着,问题是测试用例就离谱还有连long范围都超了的,学到了k神的操作了,666;

代码

class Solution {
public:
    int strToInt(string str) {
        int size = str.size();
        if (size == 0) return 0;

        int i=0;
        while (str[i] == ' ') //处理空格
            if (++i == size) return 0;

        int sign = 1;//处理符号
        if(str[i] == '-') sign = -1;
        if(str[i] == '-' || str[i] == '+') i++;

        int ans=0,bndry = INT_MAX / 10;//防止溢出
        while(str[i] >= '0' && str[i] <= '9'){
            if (ans > bndry || ans == bndry && (str[i]-'0') > 7) 
                return sign == 1 ? INT_MAX : INT_MIN;
            ans = ans*10 + (str[i]-'0');//转整数
            i++;
        }

        return sign * ans;//不忘符号
    }
};

总结

这部分内容分几次安排才弄完,有数个题目有偷瞄或者搜索语法等的行为才敲出了过了,有2题对于第一遍的解法只有大概印象,思路混乱了,代码也敲不出来,最后只能去看了题解,我太菜了,所以,部分题目我还会再去尝试独立敲,直到能流畅的敲出来为止!

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值