【剑指offer刷题】数据结构

记录在Leetcode刷《剑指offer》的笔记,希望提高自己的算法基础和编程水平。这一篇文章刷的是数据结构的题目集合,在CSDN做一下记录,随时更新,一起学习吧。

刷题链接:https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/55187i/

1. 剑指 Offer 05. 替换空格

  • 题目

在这里插入图片描述

  • 提交答案:

思路:定义一个空字符串myStr,遍历输入字符串s,遇到空格就把"%20"加入新字符串myStr,否则就添加s[i]。

class Solution {
public:
    string replaceSpace(string s) {
        string str = "%20";
        string myStr;
        for(int i = 0; i < s.size(); i++)
        {
            if(s[i] == ' ')
            {
                myStr += str;
            }
            else
            {
                myStr += s[i];
            }
        }
        return myStr;
    }
};
  • 原题解答:

思路:先计算空格数,对原string扩容,然后倒序遍历空格,依次加入’%’, ‘2’, ‘0’。

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0, len = s.size();
        // 统计空格数量
        for (char c : s) {
            if (c == ' ') count++;
        }
        // 修改 s 长度
        s.resize(len + 2 * count);
        // 倒序遍历修改
        for(int i = len - 1, j = s.size() - 1; i < j; i--, j--) {
            if (s[i] != ' ')
                s[j] = s[i];
            else {
                s[j - 2] = '%';
                s[j - 1] = '2';
                s[j] = '0';
                j -= 2;
            }
        }
        return s;
    }
};

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

  • 题目
    在这里插入图片描述

  • 提交答案

思路:遍历list,依次赋值给vector,然后利用reverse函数对vector反转。

这里用的reverse属于算法的函数,其实也可以直接对list使用,不过这里只给了头指针。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> vec;
        auto p = head;
        while(p != NULL)
        {
            vec.push_back(p->val);
            p = p->next;
        }
    reverse(vec.begin(), vec.end());
    return vec;
    }
};
  • 原题解答

思路:递归法,递归原本就属于一种逆序运行的算法。

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        recur(head);
        return res;
    }
private:
    vector<int> res;
    void recur(ListNode* head) {
        if(head == nullptr) return;
        recur(head->next);
        res.push_back(head->val);
    }
};

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

  • 题目

在这里插入图片描述

  • 提交答案

思路:使用A stack存储队列,使用B stack用于删除。由于每次都要清空B,提交的答案耗时间太长。。

class CQueue {
private:
    stack<int> A;//存储队列
    stack<int> B;//用作删除元素

public:
    CQueue() {
        
    }
    
    void appendTail(int value) {
        A.push(value);
    }
    
    int deleteHead() {
        if(A.size() == 0)
        return -1;
        while(A.size() !=0)
        {
            int x = A.top();
            A.pop();//删除
            B.push(x);
        }
        int temp = B.top();
        B.pop();
        while(B.size() != 0)
        {
            int x = B.top();
            B.pop();
            A.push(x);
        }
        return temp;
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */
  • 解题思路

思路:同样是使用A和B,A用于保存,B用于删除,不过B不清空,只要有就可以删除。

class CQueue {
public:
    stack<int> A, B;
    CQueue() {}
    void appendTail(int value) {
        A.push(value);
    }
    int deleteHead() {
        if(!B.empty()) {
            int tmp = B.top();
            B.pop();
            return tmp;
        }
        if(A.empty()) return -1;
        while(!A.empty()) {
            int tmp = A.top();
            A.pop();
            B.push(tmp);
        }
        int tmp = B.top();
        B.pop();
        return tmp;
    }
};

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

  • 题目

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、"-1E-16"、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、"±5"及"12e+5.4"都不是。

  • 提交答案

思路太繁琐

  • 题目解析

用哈希表来表示。状态是按照string从左只右依次定义的,输入数据后,状态会跳转。

在这里插入图片描述
代码示例:

说明:

  1. states是个数组,表示9中状态。数组的元素为unordered_map类型。
  2. d表示数字
  3. s表示正负号
  4. c表示dot或者空格
find(key);//查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
class Solution {
private:
    unordered_map<char,int> states[9]={
        {{' ',0}, {'s',1}, {'d',2}, {'.',4}},   //! 0.
        {{'d',2}, {'.',4}},                     //! 1.
        {{'d',2}, {'.',3}, {'e',5}, {' ',8}},   //! 2.
        {{'d',3}, {'e',5}, {' ',8}},            //! 3.
        {{'d',3}},                              //! 4.
        {{'s',6}, {'d',7}},                     //! 5.
        {{'d',7}},                              //! 6.
        {{'d',7}, {' ',8}},                     //! 7.
        {{' ',8}}                               //! 8.
    };
public:
    bool isNumber(string s) {
        int p=0;
        char t;
        for(auto c:s){
            if(c>='0'&&c<='9') t='d';
            else if(c=='+'||c=='-') t='s';
            else if(c=='e'||c=='E') t='e';
            else if(c=='.'||c==' ') t=c;
            else t='?';
            auto it=states[p].find(t);
            if(it==states[p].end()) return false;//没有找到
            p=(int)it->second;//提取下一个状态
        }//遍历结束后,查看p的状态
        return p==2||p==3||p==7||p==8;
    }
};

5. 剑指 Offer 24. 反转链表

在这里插入图片描述

  • 提交答案

思路:分3种情况,1个元素,2个元素,3个元素。最主要的是3个元素,断开链接进行移动。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL)
        return NULL;
        
        auto p1 = head;
        p1 = p1->next;
        if(p1 == NULL)//只有一个
        {
            return head;
        }
        else if(p1->next == NULL)//2个
        {
            head->next = NULL;//补null
            p1->next = head;
            return p1;
        }
        else//3个以上
        {
            head->next = NULL;//补null
            auto p2 = p1->next;
            while(p2 != NULL)
            {
                //head和p1交换
                p1->next = head;

                //移动
                head = p1;
                p1 = p2;
                p2 = p2->next;
            }
            p1->next = head;
            return p1;
        }
    }
};
  • 题目解析

思路:只需要两个指针遍历,只需要定义一个暂时的指针,保存下一个地址。代码量少耗内存较多。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *cur = head, *pre = nullptr;
        while(cur != nullptr) {
            ListNode* tmp = cur->next; // 暂存后继节点 cur.next
            cur->next = pre;           // 修改 next 引用指向
            pre = cur;                 // pre 暂存 cur
            cur = tmp;                 // cur 访问下一节点
        }
        return pre;
    }
};

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

  • 题目在这里插入图片描述
  • 题目解析

思路:A是正常栈,B为非严格排序栈。只需要A和B的栈顶元素相同时,才弹出B。

class MinStack {
public:
    stack<int> A, B;
    MinStack() {}
    void push(int x) {
        A.push(x);
        if(B.empty() || B.top() >= x)//递减排序
            B.push(x);
    }
    void pop() {
        if(A.top() == B.top())
           	B.pop();
        A.pop();
    }
    int top() {
        return A.top();
    }
    int min() {
        return B.top();
    }
};

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

在这里插入图片描述

  • 提交代码

思路:这题主要是考察string各种方法的熟练程度。截取字符串,然后删除提取部分,赋值末尾即可。

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        string str;
        str.assign(s, 0, n);//从0开始的n个字符,也可以用string的substr方法
        s.erase(0, n);//删除0到n
        for(int i = 0; i < n; i++)
        {
            s += str[i];
        }
        return s;
    }
};
  • 题目解析

思路:方法更简单明了。

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        return s.substr(n, s.size()) + s.substr(0, n);
    }
};

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

在这里插入图片描述

提交答案

思路:先写一个滑动窗口k最大值函数,然后在滑动时每次调用该函数。时间复杂度比较高,属于暴力方法。

class Solution {
public:
    int find_max(vector<int>& nums)
    {
        int max_num = nums[0];
        for(int i = 1; i < nums.size(); i++)//找出最大值
        {
            if(nums[i] > max_num)
                max_num = nums[i];
        }
        return max_num;
    }
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(nums.size() == 0)
        {
            return nums;
        }
        vector<int>temp;
        for(int i = 0; i < nums.size() - k + 1; i++)
        {
            vector<int>temp_nums;
            for(int j = 0; j < k; j++)
            {
                temp_nums.push_back(nums[i + j]);
            }
            temp.push_back(find_max(temp_nums));
        }
        return temp;
    }
};
  • 题目解析

思路:双端队列(还没研究)

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        //判断特殊情况
        int n = nums.size();
        if(n < k || k <= 0) return {};
        //滑动窗口的前后指针
        int low =  1 - k,high = 0; 
        
        deque<int> dq;//双端队列
        vector<int> res;
        
        while(high < n){
            //判断滑窗的low端是否是最大的元素
            if(low >= 1 && nums[low - 1] == dq[0]) dq.pop_front();
 
            while(!dq.empty() && dq[0] < nums[high]) dq.pop_front();//小于nums[high]的元素出队
            while(!dq.empty() && dq[dq.size()-1] < nums[high]) dq.pop_back();//小于nums[high]的元素出队

            //此时的high指针进队
            dq.push_back(nums[high]);

            if(low >= 0) res.push_back(dq[0]);//当low >= 0,滑窗已经形成
            low ++;
            high ++;
        }
        return res;
    }
};

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

在这里插入图片描述

  • 题目解析

思路:这题和包含min的栈思路一致。不过好像stack存储空间不够。使用双向队列。

class MaxQueue {
    queue<int> que;
    deque<int> deq;
public:
    MaxQueue() { }
    int max_value() {
        return deq.empty() ? -1 : deq.front();
    }
    void push_back(int value) {
        que.push(value);
        while(!deq.empty() && deq.back() < value)
            deq.pop_back();
        deq.push_back(value);
    }
    int pop_front() {
        if(que.empty()) return -1;
        int val = que.front();
        if(val == deq.front())
            deq.pop_front();
        que.pop();
        return val;
    }
};

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

写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。

首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。

当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。

该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。

注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。

在任何情况下,若函数不能进行有效的转换时,请返回 0。

说明:

假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

示例 1:

输入: “42”
输出: 42
示例 2:

输入: " -42"
输出: -42
解释: 第一个非空白字符为 ‘-’, 它是一个负号。
我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。

示例 3:

输入: “4193 with words”
输出: 4193
解释: 转换截止于数字 ‘3’ ,因为它的下一个字符不为数字。
示例 4:

输入: “words and 987”
输出: 0
解释: 第一个非空字符是 ‘w’, 但它不是数字或正、负号。
因此无法执行有效的转换。
示例 5:

输入: “-91283472332”
输出: -2147483648
解释: 数字 “-91283472332” 超过 32 位有符号整数范围。
因此返回 INT_MIN (−231) 。

思路:4种情况,依次列举。

  1. 首部空格
  2. 符号位
  3. 非数字字符
  4. 数字拼接

利用字符减去asii码,即可得到数字位。最主要的是要熟悉int位的最大值为-2147483648-2147483647

题目解析:

class Solution {
public:
    int strToInt(string str) {
        int res = 0, bndry = INT_MAX / 10;
        int i = 0, sign = 1, length = str.size();
        if(length == 0) return 0;
        while(str[i] == ' ')
            if(++i == length) return 0;
        if(str[i] == '-') sign = -1;
        if(str[i] == '-' || str[i] == '+') i++;
        for(int j = i; j < length; j++) {
            if(str[j] < '0' || str[j] > '9') break;
            if(res > bndry || res == bndry && str[j] > '7')
                return sign == 1 ? INT_MAX : INT_MIN;
            res = res * 10 + (str[j] - '0');
        }
        return sign * res;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非晚非晚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值