Week11. 第225-234题

225. 用队列实现栈

分析

压入

压入的话, 直接把5压入队列

弹出

弹出的话, 按照栈的弹出方式, 应该将5弹出, 但是我们按照队列的话, 会把1弹出去
所以我们先想办法把5找到, 再把5弹出去

在这里插入图片描述

所以这里我们需要用一个额外的缓存的点, 先将1234从队列里弹出来, 然后存起来
然后队列里就不存在1234了

然后这样的话, 5就找到了, 将5弹出来, 弹完之后,

我们将1234 放回去
这个缓存的数据结构也是队列
所以我们从缓存中往外去弹, 也是1234往外去弹

因此再把1234顺次放到队列就可以了
在这里插入图片描述

在这里插入图片描述
这样的话弹出就有了

返回栈顶

返回栈顶的也是类似的操作, 如果想返回栈顶的话 , 我们想将1, 2, 3, 4存到缓存队列里, 存完之后将5输出, 输出之后,!!!要注意下, 要先将5弹出来, 存到缓存队列里, 存完之后再把1-5, 放到原队列

放到原队列图
在这里插入图片描述

是否为空

是否为空的话, 直接返回队列是否为空

code

class MyStack {
public:
    /** Initialize your data structure here. */
    queue<int> q, w;
    MyStack() {

    }
    
    /** Push element x onto stack. */
    void push(int x) {
        q.push(x);
    }
    
    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        while (q.size() > 1) w.push(q.front()), q.pop(); // 队列元素 > 1个的时候, 将其余元素存起来
        int t = q.front(); q.pop(); // 弹出队头
        while (w.size()) q.push(w.front()), w.pop(); // 将原来的按顺序放回去
        return t;
    }
    
    /** Get the top element. */
    int top() {
        while (q.size() > 1) w.push(q.front()), q.pop();
        int t = q.front(); q.pop();
        while (w.size()) q.push(w.front()), w.pop();
        q.push(t);// 队尾插入原来的数
        return t;
    }
    
    /** Returns whether the stack is empty. */
    bool empty() {
        return q.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

226. 翻转二叉树

分析

关于中间轴作“轴对称”, 首先需要将根节点的两个儿子对调
不能将2这个子树移过去, 还要将2这个子树本身作一个翻转, 因为这是一个轴对称的结果

所以要将整颗子树翻转的话, 首先需要将两颗子树对调, 对调完后你需要递归的翻转左右两颗子树本身

所以其实是一个递归的问题

code

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (!root) return nullptr;
        swap(root->left, root->right); // 先翻转左右两颗子树
        invertTree(root->left); // 再递归翻转左子树
        invertTree(root->right); // 再递归翻转右子树
        return root;
    }
};

227. 基本计算器 II

分析

  1. 如果前面的优先级(栈顶)>= 后面的优先级
    举个例子, 比如a x b + c, 那么就是上面说的情况, 因此a x b的结果可以先算
    在这里插入图片描述

code

class Solution {
public:
    stack<int> num;
    stack<char> op;
    
    void eval() {
        int b = num.top(); num.pop();
        int a = num.top(); num.pop();
        char c = op.top(); op.pop();
        if (c == '+') a += b;
        else if (c == '-') a -= b;
        else if (c == '*') a *= b;
        else a /= b;
        num.push(a);
    }

    int calculate(string s) {
        unordered_map<char, int> pr;
        pr['+'] = 1, pr['-'] = 1, pr['*'] = 2, pr['/'] = 2;
        for (int i = 0 ; i < s.size(); i ++ ){
            char c = s[i];
            if (c == ' ') continue;
            else if (isdigit(c)){
                int x = 0, j = i;
                while (j < s.size() && isdigit(s[j])) x = x * 10 + (s[j ++ ] - '0');
                num.push(x);
                i = j - 1;
            }else {
                while (op.size() && pr[op.top()] >= pr[c]) eval();
                op.push(c);
            }
        }
        while (op.size()) eval();
        return num.top();
    }
};

228. 汇总区间

分析

是一个双指针的题目
从前往后扫描一边就可以了, 从第1个数开始扫描, 扫描的时候看下
以第1个数为起点的区间, 最长有多长, 这里再搞一个新的指针(j = i + 1),

只要第2个指针指向的数比前一个的数大1, 说明是连续的一段, 就往后走, 直到走到相差比较多为止, 说明我们找到一段区间

找完之后的话, 把这个区间输出

输出完之后把第1个指针跳到后面这个位置

样例模拟:

4和2不邻接, 因此找到第1个区间
在这里插入图片描述

找到第1个区间后, 让第1个指针从4开始走, 然后第2个指针从第1个指针的后面开始走
然后7和5不邻接
在这里插入图片描述

然后第2个区间也有了
然后再让第1个指针指向7, 第2个指针指向后面, 那么最后一个数就是一个区间

code

class Solution {
public:
    vector<string> summaryRanges(vector<int>& nums) {
        vector<string> res;
        for (int i = 0, j = 0; i < nums.size(); i ++ ){
            j = i + 1;
            while (j < nums.size() && nums[j] == nums[j - 1] + 1) j ++ ;
            if (j == i + 1) res.push_back(to_string(nums[i])); // 只有1个数
            else {
                res.push_back(to_string(nums[i]) + "->" + to_string(nums[j - 1]));
            }
            i = j - 1;
        }
        return res;
    }
};

229. 求众数 II

分析

k = 2的话, 只有1个仓库
k = 3的话, 需要有2个仓库, r1仓库, r2仓库
对于当前的数x, 看下是否和当前某个仓库内的数一样, 如果一样, 就把对应的x加到仓库里; 比方说x与r1是一样的, 就加到r1仓库里
如果和r2一样, 就加到r2仓库里;
如果和r1, r2都不一样的话, 看下某个仓库是否为空; 如果r1为空的话, 就把x放到r1里; 如果r2为空, 就把x放到r2里
再否则, 如果当前两个仓库都不空, 并且x与r1, r2仓库的数都不一样的话, 那么就需要从两个仓库同时拿出来一个物品, 与x抵消掉

r1, r2 一定不相等, 因为和r1相等放到r1里了, 和r2相等就放到r2里了

做完后, r1, r2仓库存的数, 不一定满足要求, 最后还要扫描一遍, 把满足要求的数输出
即: 如果一个数出现次数> n / 3的话, 那么必然存到r1, r2里

这种情况, 可以推广到k的时候

推广

可以找到出现次数严格大于 n k \frac{n}{k} kn的所有数, 可以在 O ( k n ) O(kn) O(kn)的时间复杂度内, 找到结果

摩尔投票法

证明正确性

如果某个数次数 > n 3 \frac{n}{3} 3n, 为什么一定会

x 0 > n 3 x_0 > \frac{n}{3} x0>3n, 但是不在两个仓库里, 那么 x 0 x_0 x0必然会被消耗掉
看下 x 0 x_0 x0会在什么情况下被消耗掉

  1. 当前枚举的 x 0 x_0 x0, 并且 x 0 x_0 x0与两个仓库中的数都不一样, 就会消耗掉 x 0 x_0 x0

发现消耗 x 0 x_0 x0的话, 要消耗第1个仓库的数, 同时要消耗第2个仓库的数. 相当于每消耗 x 0 x_0 x0都会陪葬两个数

2.在这里插入图片描述
当前 x 0 x_0 x0 已经在某个仓库里了, 来了一个数 x ′ x' x x 0 x_0 x0不一样, x ′ x' x如果能够消耗 x 0 x_0 x0, 那么必然也和第2个仓库的数不一样. 那么发现 x 0 x_0 x0 消耗过程中, 也会有两个数陪葬

所以我们会发现, 不管 x 0 x_0 x0在什么情况下被消耗, 都需要两个额外的数来陪葬

由于 x 0 x_0 x0出现次数 > ⌊ n 3 ⌋ >\lfloor\frac{n}{3}\rfloor >3n, 就算把剩余所有数拿来陪葬 x 0 x_0 x0的话, 那么 x 0 x_0 x0也会剩余1个, 所以发现 x 0 x_0 x0一定会剩下来

从数学上证明 x 0 x_0 x0出现次数 > ⌊ n 3 ⌋ >\lfloor\frac{n}{3}\rfloor >3n, 那么 x 0 x_0 x0出现次数 ≥ ⌊ n 3 ⌋ + 1 \geq \lfloor\frac{n}{3}\rfloor + 1 3n+1

如果想消耗 x 0 x_0 x0, 必须得3倍的 ⌊ n 3 ⌋ + 1 \lfloor\frac{n}{3}\rfloor + 1 3n+1,
⌊ n 3 ⌋ ∗ 3 + 3 \lfloor\frac{n}{3}\rfloor * 3 + 3 3n3+3

可以计算得出 ⌊ n 3 ⌋ ∗ 3 + 3 > n \lfloor\frac{n}{3}\rfloor * 3 + 3 > n 3n3+3>n.
在这里插入图片描述

因此不管什么情况 ⌊ n 3 ⌋ ∗ 3 + 3 > n \lfloor\frac{n}{3}\rfloor * 3 + 3 > n 3n3+3>n. 也就是说如果想把 x x x消耗掉的话, 需要的数要 > n >n >n, 但是一共只有n个数, 所以矛盾

k是任意值

只需要开k - 1个仓库, 如果一个数出现次数> n k \frac{n}{k} kn, 如果这个数没有留下来, 那么必然是被消耗掉

那么如果想消耗这个数
1. x 0 x_0 x0是在外面的, 那么如果想消耗 x 0 x_0 x0, 就需要 k - 1个数陪葬
2. x 0 x_0 x0在仓库里面, 同理, 如果想消耗 x 0 x_0 x0, 也需要k - 1个数陪葬(k - 2个仓库的数, 以及一个x’在外面与x_0和k - 2个仓库的数不同)
因此要消耗 x 0 x_0 x0的话, 就总共需要消耗k个数, 由于 x 0 x_0 x0出现次数>k(???), 所以 x 0 x_0 x0一定会剩余

code

class Solution {
public:
    vector<int> majorityElement(vector<int>& nums) {
        int n = nums.size();
        int r1, r2, c1 = 0, c2 = 0;
        for (auto x : nums){ 
        	// 一定要先判断两个仓库的情况
            if (r1 == x) c1 ++;
            else if (r2 == x) c2 ++;
            else if (!c1) r1 = x, c1 ++;
            else if (!c2) r2 = x, c2 ++;
            else c1 --, c2 --;
        }
        c1 = 0, c2 = 0;
        for (auto& x : nums){
            if (x == r1) c1 ++;
            else if (x == r2) c2 ++;
        }
        vector<int> res;
        if (c1 > n / 3) res.push_back(r1);
        if (c2 > n / 3) res.push_back(r2);

        return res;
    }
};

230. 二叉搜索树中第K小的元素

分析

因为中序遍历是有序的, 求二叉搜索树的第k小元素 等价于求二叉搜索树中序遍历的第k个数

所以中序遍历的时候, 搞个全局变量, 当遍历到第k个数的时候, 停下来就可以了

时间复杂度O(k), 因为一共会遍历k次

进阶要求

需要频繁的插入删除, 是平衡树的内容
首先TreeNode节点里需要记录额外的值, 维护下以这个点为根的子树的里面一共有多少个点, 这个是可以在插入和删除中维护好的

有了这样一个信息后, 递归的时候就只用O(logn)的时间复杂度去算这个问题了

由于每个点都记录了以每个点为根的子树中有多少个元素, 所以左子树有多少个元素, 可以在左子树中找出来

直接看下左子树的LC(个数) >= k的话, 说明第k小元素一定在左子树里, 因为左子树是当前最小的元素, 那么只需要递归左子树就可以了

否则, 如果左子树元素LC + 1 = k的话, 第k小元素恰好就是根节点

再否则, 如果LC + 1 > k的话, 那么第k小元素, 第k小元素是在右子树, 那么递归右子树

这样的话可以发现每次只会走到一个分支里

平衡树的高度是O(logn)的, 因此时间复杂度是O(logn)的

code

因为要寻找正确的结果,所以递归只能用
if (dfs(root->left) return true;
不能写成if (!dfs(root->left)) return false; 这样只能找false的结果

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int k;
    int ans;
    int kthSmallest(TreeNode* root, int _k) {
        k = _k;
        dfs(root);
        return ans;
    }

    bool dfs(TreeNode* root){
        if (!root) return false;
        if (dfs(root->left)) return true;
        if (-- k == 0){
            ans = root->val;
            return true;
        }
        return dfs(root->right);
    }
};

231. 2的幂

分析

考虑一个数什么时候是2的幂, 必然是这样的 ( 10000...00 ) 2 (10000...00)_2 (10000...00)2
可以回想起lowbit函数, lowbit会返回最后1位1
如果一个数是2的整数幂, 那么就会返回 ( 10000...00 ) 2 (10000...00)_2 (10000...00)2, 也就会返回自身

code

class Solution {
public:
    bool isPowerOfTwo(int n) {
        return n > 0 && n == (n & -n);
    }
};

232. 用栈实现队列

分析

不管3721, 直接存栈里

pop, peek

比如说要找1, 先开个缓存的栈,
在这里插入图片描述
3, 2拿出来, 放到另外的栈里, 1自然就在最上面了, 就可以拿出来了
在这里插入图片描述

拿完之后 将2, 3放回去, 先将2压回去, 再把3压回去

code

class MyQueue {
public:
    /** Initialize your data structure here. */
    stack<int> a, b;
    MyQueue() {

    }
    
    /** Push element x to the back of queue. */
    void push(int x) {
        a.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        while (a.size() > 1) b.push(a.top()), a.pop();
        int t = a.top(); a.pop();
        while (b.size()) a.push(b.top()), b.pop();
        return t;
    }
    
    /** Get the front element. */
    int peek() {
        while (a.size() > 1) b.push(a.top()), a.pop();
        int t = a.top(); // 实现栈的话, 最后一个元素本来就在栈底, 就不需要删除了
        while (b.size()) a.push(b.top()), b.pop();
        return t;
    }
    
    /** Returns whether the queue is empty. */
    bool empty() {
        return a.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

233. 数字 1 的个数

分析

在这里插入图片描述
怎么统计1出现的次数呢,
我们统计下0 ~ abcdefg, 每一位上1出现了多少次

如果d = 0的话, 那么左边可以取0 ~ abc - 1, 因为高位取的是0 ~ abc - 1, 所以

13015:
1.万位的1, 10000~ 13015, 总共3016个次1
2.千位出现1: 当万位是0的时候, 1000 ~ 1999(1000次), 万位是1的时候 11000 ~ 11999(1000次), 总共2000次
3.百位出现1 :
_ _ 1 _ _ , 首先考虑前2位, 如果是00 ~ 12, 后面两位都可以从00~99, 因此13 * 100 = 1300种
第2种情况是, 前2位封顶了13_ _ , 百位不可能是1, 因为131xx肯定大于13015, 所以此种情况不可能
4. 十位:
_ _ _ 1 _ , 可以发现 前面000 ~ 129, 后面0~9随便选, 130 * 10 = 1300种
前面130封顶了, 十位是1, 1301
, 此时个位有几种选法呢(0 ~ 5, 6种选法)

所以可以总结下: 原数是abcdef
比如c = 1
1.最高位可以是0 ~ ab - 1, 因为前面没有顶到最高位, 所以后面可以随便选, 后面是000~ 999, 因此方案是 ab* 1000
2.最高位封顶ab,
如果原数中c = 0, 那么枚举的时候, 就不能枚举c = 1的情况了, 所以0个1
如果原数中c = 1, 可以枚举到c = 1的情况, 那么需要看一下def, 那么后面的选法只能0-def(def+1种)
如果原数中c > 1, 那么c枚举1的时候, def位可以从000~ 999(1000次)

举个例子:
123456 c = 3, 那么c枚举 = 1的时候, 后3位可以取000~999, 因为都在范围内

按位来做的, 一个数最多 l o g 10 n log_{10} n log10n
在这里插入图片描述

code

class Solution {
public:
    int countDigitOne(int n) {
        vector<int> nums;
        while (n) nums.push_back(n % 10), n /= 10;
        int res = 0;
        for (int i = nums.size() - 1; i >= 0; i -- ){
            auto left = 0, right = 0, t = 1;
            for (int j = nums.size() - 1; j > i; j -- ) left = left * 10 + nums[j];
            for (int j = i - 1; j >= 0; j -- ) right = right * 10 + nums[j], t *= 10;
            // 考虑第1部分的值 00 ~ ab - 1, 后面可以取000~999
            res += left * t;
            // 前两位封顶ab
            // 如果原数中c = 1, 那么后面可以取0 ~ def + 1
            if (nums[i] == 1) res += right + 1;
            // 如果原数中c > 1, 那么后面可以取 0 ~ 999
            else if (nums[i] > 1) res += t;
        }
        return res;
    }
};

234. 回文链表

分析

怎么判断是不是回文链表

一个指针从前往后走, 另一个指针从后往前走, 每次判断下两个指针是否一样, 两个指针相遇的时候, 就判断完了

但是链表不能从后往前走

我们希望判断前面n / 2和后面n / 2是否一样, 其中希望后面一半可以从后往前走

所以需要将后面的指针翻转一下

这样就可以将两个指针一个➡️, 一个⬅️, 走n / 2步

做完之后一定要将链表恢复原状

要将后面 n / 2个元素翻转, 就需要先找到如下的点
要循环多少次, 可以从头循环到这个点呢
前面一共 n - half个点, (因为当前点(蓝色箭头) 到最末尾一共有half个点哇), 因此需要跳 n - half次, 因为需要跳到中点的后一个点
如果是跳到中点的话只用(n - half - 1)次
在这里插入图片描述
然后一共要翻转 half - 1次
在这里插入图片描述
翻转完后,
在这里插入图片描述
判断回文后, 需要将链表恢复原状

让a, b分别指向以下两点, 再来一遍就可以了
在这里插入图片描述

code

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int n = 0;
        for (auto p = head; p; p = p->next) n ++ ;
        if (n <= 1) return true;

        int half = n / 2;
        auto a = head;
        // 找到需要翻转的第1个点
        for (int i = 0; i < n - half; i ++ ) a = a->next;

        auto b = a->next;
        // 翻转
        while (b){
            auto c = b->next;
            b->next = a;
            a = b, b = c;
        }

        auto tail = a;
        auto p = head, q = a;
        bool success = true;
        // 判断回文
        for (int i = 0; i < half; i ++ ) {
            if (p->val != q->val) {
                success = false;
                break;
            }
            p = p->next, q = q->next; // 记住一定要移动到下一个位置, 如果值相等的话
        }

        b = a->next;

        // 恢复原状
        for (int i = 0; i < half - 1; i ++ ){
            auto c = b->next;
            b->next = a;
            a = b, b = c;
        }

        // 尾巴置空
        tail->next = NULL;
        return success;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园失物招领系统管理系统按照操作主体分为管理员和用户。管理员的功能包括字典管理、论坛管理、公告信息管理、失物招领管理、失物认领管理、寻物启示管理、寻物认领管理、用户管理、管理员管理。用户的功能等。该系统采用了Mysql数据库,Java语言,Spring Boot框架等技术进行编程实现。 校园失物招领系统管理系统可以提高校园失物招领系统信息管理问的解决效率,优化校园失物招领系统信息处理流程,保证校园失物招领系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 ,管理员权限操作的功能包括管理公告,管理校园失物招领系统信息,包括失物招领管理,培训管理,寻物启事管理,薪资管理等,可以管理公告。 失物招领管理界面,管理员在失物招领管理界面中可以对界面中显示,可以对失物招领信息的失物招领状态进行查看,可以添加新的失物招领信息等。寻物启事管理界面,管理员在寻物启事管理界面中查看寻物启事种类信息,寻物启事描述信息,新增寻物启事信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值