剑指offer刷题笔记--Num31-40

文章详细阐述了栈的压入、弹出序列验证,以及不同方式从上到下打印二叉树的方法,包括层次遍历的不同变体。同时介绍了二叉搜索树的后序遍历序列验证,二叉树中和为特定值的路径查找,复杂链表的复制,如何将二叉搜索树转换为双向链表,序列化二叉树,字符串的全排列,以及找出数组中出现次数超过一半的数字和最小的k个数的算法。这些内容涵盖了数据结构和算法的基础与应用。
摘要由CSDN通过智能技术生成

目录

1--栈的压入、弹出序列(31)

2--从上到下打印二叉树(32)

3--从上到下打印二叉树II(32)

4--从上到下打印二叉树III(32)

5--二叉搜索树的后序遍历序列(33)

6--二叉树中和为某一值的路径(34)

7--复杂链表的复制(35)

8--二叉搜索树与双向链表(36)

9--序列化二叉树(37)

10--字符串的排列(38)

11--数组中出现次数超过一半的数字(39)

12--最小的 k 个数(40)


1--栈的压入、弹出序列(31)

直观思路:用两个指针 i 和 j 指向压入和弹出的 vector,终止条件是:所有元素都压入了辅助栈(i > len),且辅助栈当前的栈顶元素与弹出的元素 popped[j] 不相等;

#include <iostream>
#include <vector>
#include <stack>

class Solution {
public:
    bool validateStackSequences(std::vector<int>& pushed, std::vector<int>& popped) {
        int len = pushed.size();
        if (len == 0) return true;
        int i = 0, j = 0; // i 指向 pushed 的元素,j 指向 popped 的元素
        while(i < len || !st.empty()){
            if (i >= len && st.top() != popped[j]){ // 栈顶元素不等于当前要pop的元素,且已push完所有元素
                return false;
            }

            if (!st.empty() && st.top() == popped[j]){ // 如果栈顶元素等于当前要pop的元素,pop出栈顶元素,j下移
                st.pop();
                j++;
            }
            else if(i < len && pushed[i] != popped[j]){ // i 指向的元素不等于 j 指向的元素,将 i 压入堆栈中
                st.push(pushed[i]);
                i++;
            }
            else if(i < len && pushed[i] == popped[j]){ // i 指向的元素等于 j 指向的元素,i,j移向下一位
                i++;
                j++;
            }
        }
        return true;
    }

private:
    std::stack<int> st;
};

int main(int argc, char *argv[]){
    std::vector<int> push = {2, 1, 0};
    std::vector<int> pop = {1, 2, 0};
    Solution S1;
    bool res = S1.validateStackSequences(push, pop);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

简便思路:

        模拟栈的压入顺序,依次压入 pushed 里的所有元素,用一个指针 j 指向需要弹出的元素,当符合弹出要求时就弹出对应的元素;

        当辅助栈为空时,表明需要弹出的元素都顺利弹出,返回 true;

#include <iostream>
#include <vector>
#include <stack>

class Solution {
public:
    bool validateStackSequences(std::vector<int>& pushed, std::vector<int>& popped) {
        std::stack<int> st;
        int n = pushed.size();
        for (int i = 0, j = 0; i < n; i++) {
            st.emplace(pushed[i]); 
            while (!st.empty() && st.top() == popped[j]) { // 符合弹出要求,直接 pop 出栈顶元素
                st.pop();
                j++;
            }
        }
        return st.empty(); // 所有元素都顺利pop出,返回true;
    }
};

int main(int argc, char *argv[]){
    std::vector<int> push = {2, 1, 0};
    std::vector<int> pop = {1, 2, 0};
    Solution S1;
    bool res = S1.validateStackSequences(push, pop);
    if(res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

2--从上到下打印二叉树(32)

主要思路:层次遍历打印二叉树结点

#include <iostream>
#include <vector>
#include <queue>

struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    std::vector<int> levelOrder(TreeNode* root) {
        if (root == NULL) return Res;
        Q1.push(root);
        while(!Q1.empty()){
            temp = Q1.front();
            Q1.pop();
            Res.push_back(temp->val);
            if(temp->left != NULL){
                Q1.push(temp->left);
            }
            if(temp->right != NULL){
                Q1.push(temp->right);
            }
        }
        return Res;
    }

private:
    std::queue<TreeNode *> Q1;
    std::vector<int> Res;
    TreeNode *temp;
};

int main(int argc, char *argv[]){
    TreeNode *Node1 = new TreeNode(3);
    TreeNode *Node2 = new TreeNode(9);
    TreeNode *Node3 = new TreeNode(20);
    TreeNode *Node4 = new TreeNode(15);
    TreeNode *Node5 = new TreeNode(7);
    Node1->left = Node2;
    Node1->right = Node3;
    Node3->left = Node4;
    Node3->right = Node5;

    Solution s1;
    std::vector<int> res = s1.levelOrder(Node1);
    for(int item : res){
        std::cout << item << " ";
    }
    return 0;
}

3--从上到下打印二叉树II(32)

主要思路:

        与上题类似,借助于层次遍历,不同的是为了打印每一层的结点,需要循环当前层的结点数次,每一次当前队列的结点数实质上等于当前层的结点数,因此只需要循环队列的长度次,并记录对应的结点值即可;

#include <iostream>
#include <vector>
#include <queue>

struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    std::vector<std::vector<int>> levelOrder(TreeNode* root) {
        if(root == NULL) return Res;
        Q1.push(root);
        while(!Q1.empty()){
            int num = Q1.size(); // 当前层的结点数
            for(int i = 0; i < num; i++){
                temp_node = Q1.front();
                Q1.pop();
                temp.push_back(temp_node->val);
                if(temp_node->left != NULL) Q1.push(temp_node->left); 
                if(temp_node->right != NULL) Q1.push(temp_node->right);
            }    
            Res.push_back(temp);
            temp.clear();
        }
        return Res;
    }
private:
    std::vector<std::vector<int>> Res;
    std::queue<TreeNode *> Q1;
    std::vector<int> temp;
    TreeNode * temp_node;
};

int main(int argc, char *argv[]){
    TreeNode *Node1 = new TreeNode(3);
    TreeNode *Node2 = new TreeNode(9);
    TreeNode *Node3 = new TreeNode(20);
    TreeNode *Node4 = new TreeNode(15);
    TreeNode *Node5 = new TreeNode(7);
    Node1->left = Node2;
    Node1->right = Node3;
    Node3->left = Node4;
    Node3->right = Node5;

    Solution s1;
    std::vector<std::vector<int>> res = s1.levelOrder(Node1);
    for(int i = 0; i < res.size(); i++){
        for(int item : res[i]){
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

4--从上到下打印二叉树III(32)

主要思路:

        借助于双端队列,区分奇数偶数行,奇数行从双端队列的左边(front)取结点,并从右子结点开始不断往头插入;偶数行从双端队列的右边(back)取结点,并从左子结点开始不断往后插入;

        核心问题是区分奇数偶数行,如何取结点和插入子节点到队列中;

#include <iostream>
#include <vector>
#include <deque>

struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Solution {
public:
    std::vector<std::vector<int>> levelOrder(TreeNode* root) {
        if (root == NULL) return Res;
        bool flag = true; // 奇数行 true, 偶数行 false; 
        Q.push_back(root);
        while(!Q.empty()){
            std::vector<int> tmp;
            int num = Q.size();
            for(int i = 0; i < num; i++){
                if(flag){
                    tmp_node = Q.front();
                    Q.pop_front();
                    tmp.push_back(tmp_node->val);
                    if(tmp_node->left != NULL) Q.push_back(tmp_node->left);
                    if(tmp_node->right != NULL) Q.push_back(tmp_node->right);
                }
                else{
                    tmp_node = Q.back();
                    Q.pop_back();
                    tmp.push_back(tmp_node->val);
                    if(tmp_node->right != NULL) Q.push_front(tmp_node->right);
                    if(tmp_node->left != NULL) Q.push_front(tmp_node->left);
                }
            }
            flag = !flag;
            Res.push_back(tmp);
        }
        return Res;
    }
private:
    std::deque<TreeNode *> Q;
    std::vector<std::vector<int>> Res;
    TreeNode * tmp_node;
};

int main(int argc, char *argv[]){
    TreeNode *Node1 = new TreeNode(3);
    TreeNode *Node2 = new TreeNode(9);
    TreeNode *Node3 = new TreeNode(20);
    TreeNode *Node4 = new TreeNode(15);
    TreeNode *Node5 = new TreeNode(7);
    Node1->left = Node2;
    Node1->right = Node3;
    Node3->left = Node4;
    Node3->right = Node5;

    Solution s1;
    std::vector<std::vector<int>> res = s1.levelOrder(Node1);
    for(int i = 0; i < res.size(); i++){
        for(int item : res[i]){
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

5--二叉搜索树的后序遍历序列(33)

主要思路:

        后序遍历的定义:左→右→根;

        二叉搜索树的定义:左子树的结点均小于根节点,右子树的结点均大于根结点;左子树和右子树均为二叉搜索树;

        利用递归算法,判断左子树和右子树是否为二叉搜索树,回溯条件为树只剩下一个结点,这时(i >= j),返回 true;

#include <iostream>
#include <vector>

class Solution {
public:
    bool verifyPostorder(std::vector<int>& postorder) {
        return recur(postorder, 0, postorder.size() - 1);
    }

    bool recur(std::vector<int>& postorder, int i, int j){
        if(i >= j) return true;
        int p = i;
        while(postorder[p] < postorder[j]) p++; // 直到遍历完左子树所有结点(左子树结点都小于根结点)
        int m = p; // 右子树序列的第一个结点
        while(postorder[p] > postorder[j]) p++; // 判断右子树的结点是否都大于根结点
        // 右子树的结点都大于根节点时,p指向根节点,即 p == j 为 true
        // 递归判断左子树是否为二叉搜索树,右子树是否为二叉搜索树
        return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
    }
};

int main(int argc, char *argv[]){
    std::vector<int> test = {1, 3, 2, 6, 5};
    Solution S1;
    bool res = S1.verifyPostorder(test);
    if (res) std::cout << "true" << std::endl;
    else std::cout << "false" << std::endl;
    return 0;
}

6--二叉树中和为某一值的路径(34)

主要思路:

        基于深度优先搜索,不断搜索下一层,并更新对应的目标值;

#include <iostream>
#include <string>
#include <vector>

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:
    std::vector<std::vector<int>> pathSum(TreeNode* root, int target) {
        dfs(root, target);
        return Res;
    }

    void dfs(TreeNode* root, int target){
        if (root == NULL) return;
        path.push_back(root->val);
        target = target - root->val;
        
        if(root->left == NULL && root->right == NULL && target == 0){
            Res.push_back(path);
        }

        dfs(root->left, target);
        dfs(root->right, target);
        path.pop_back();
    }

private:
    std::vector<std::vector<int>> Res;
    std::vector<int> path;
};

int main(int argc, char *argv[]){
    // root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
    TreeNode *Node1 = new TreeNode(5);
    TreeNode *Node2 = new TreeNode(4);
    TreeNode *Node3 = new TreeNode(8);
    TreeNode *Node4 = new TreeNode(11);
    TreeNode *Node5 = new TreeNode(13);
    TreeNode *Node6 = new TreeNode(4);
    TreeNode *Node7 = new TreeNode(7);
    TreeNode *Node8 = new TreeNode(2);
    TreeNode *Node9 = new TreeNode(5);
    TreeNode *Node10 = new TreeNode(1);

    Node1->left = Node2;
    Node1->right = Node3;
    Node2->left = Node4;
    Node3->left = Node5;
    Node3->right = Node6;
    Node4->left = Node7;
    Node4->right = Node8;
    Node6->left = Node9;
    Node6->right = Node10;

    int targetSum = 22;

    Solution S1;
    std::vector<std::vector<int>> Res = S1.pathSum(Node1, targetSum);
    for(auto item : Res){
        for(int value : item){
            std::cout << value << " ";
        }
        std::cout << std::endl;
    }
    return 0;
}

7--复杂链表的复制(35)

主要思路:

        复制普通链表时,只需在遍历每一个结点时创建新的结点;但在复制复杂链表时,由于 random 指针的存在,会出现以下问题:即一个结点 random 指针指向的结点还未被创建;

        可以通过哈希表的形式将原结点和新结点进行匹配成对,创建完所有新结点后再更新 next 指针和 random 指针的关系;

#include <iostream>
#include <map>

class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};

class Solution {
public:
    Node* copyRandomList(Node* head) {
        Node* tmp = head;
        while(tmp != NULL){
            Node* newNode = new Node(tmp->val); // 创建新结点
            M.insert({tmp, newNode}); // 原结点与新结点匹配成对,原结点为 key,新结点为value
            tmp = tmp->next;
        }
        // 创建 next 指向和random指向
        tmp = head;
        while(tmp != NULL){
            M[tmp]->next = M[tmp->next];
            M[tmp]->random = M[tmp->random];
            tmp = tmp->next;
        }
        return M[head];

    }
private:
    std::map<Node*, Node*> M; // 创建哈希表
};

int main(int argc, char *argv[]){
    Node* Node1 = new Node(7);
    Node* Node2 = new Node(13);
    Node* Node3 = new Node(11);
    Node* Node4 = new Node(10);
    Node* Node5 = new Node(1);

    Node1->next = Node2;
    Node2->next = Node3;
    Node3->next = Node4;
    Node4->next = Node5;

    Node2->random = Node1;
    Node3->random = Node5;
    Node4->random = Node3;
    Node5->random = Node1;

    Solution S1;
    Node* Copy_Node = S1.copyRandomList(Node1);
    Node* tmp = Copy_Node;
    while(tmp != NULL){
        std::cout << tmp->val << " ";
        tmp = tmp->next;
    }
    return 0;
}

8--二叉搜索树与双向链表(36)

主要思路:

        中序遍历二叉搜索树,遍历过程中构建前后结点的关系;

#include <iostream>
#include <map>

class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if (root == NULL) return root; 
        dfs(root);
        // 经过中序遍历后,最小的结点还未指向最大的结点,最大的结点也还未指向最小的结点
        // 此时,head指向最小的结点,pre指向最大的结点
        head->left = pre;
        pre->right = head;
        return head;
    }

    void dfs(Node* cur){
        if(cur == NULL) return;
        // 递归左子树
        dfs(cur->left);
        if(pre == NULL){ // 前继结点为空,这时cur为最小的结点,用头指针指向
            head = cur;
        }
        else{ // 前继结点不为空
            pre->right = cur; // 前继结点右指针指向 cur
            cur->left = pre; // cur结点左指针指向pre
        }
        pre = cur; // 更新前继结点,递归右子树
        dfs(cur->right);
    }
private:
    Node* head = NULL;
    Node* pre = NULL; // 前继结点
};

int main(int argc, char *argv[]){
    Node* Node1 = new Node(4);
    Node* Node2 = new Node(2);
    Node* Node3 = new Node(5);
    Node* Node4 = new Node(1);
    Node* Node5 = new Node(3);

    Node1->left = Node2;
    Node1->right = Node3;
    Node2->left = Node4;
    Node2->right = Node5;

    Solution S1;
    Node* Res = S1.treeToDoublyList(Node1);
    Node* tmp = Res;

    for(int i = 0; i < 10; i++){
        std::cout << tmp->val << " ";
        tmp = tmp->right;
    }
    return 0;
}

9--序列化二叉树(37)

主要思路:视频讲解参考

        序列化时存储层次遍历的结果;反序列化时根据层次遍历的结果来重构二叉树;

#include <iostream>
#include <vector>
#include <string>
#include <queue>

struct TreeNode {
     int val;
     TreeNode *left;
     TreeNode *right;
     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

class Codec {
public:
    std::string serialize(TreeNode* root) {
        std::string Res;
        if(root == NULL) return Res;
        std::queue<TreeNode*> Q;
        Q.push(root);
        while(!Q.empty()){
            TreeNode* tmp = Q.front();
            Q.pop();
            if(tmp != NULL){
                Q.push(tmp->left);
                Q.push(tmp->right);
                Res.append(std::to_string(tmp->val) + ',');
            }
            else{
                Res.append("NULL,");
            }
        }
        return Res;
    }

    TreeNode* deserialize(std::string data) {
        if(data.empty()) return NULL;
        std::vector<std::string> StringList;
        std::string tmp_string;
        
        // 将字符串拆分存储到StringList中
        for(char c : data){
            if(c != ','){
                tmp_string.push_back(c);
            }
            else{
                StringList.push_back(tmp_string);
                tmp_string.clear();
            }
        }

        std::queue<TreeNode*> Q;
        TreeNode* head = new TreeNode(std::stoi(StringList[0])); // 第一个字符串的值是根节点
        Q.push(head);
        int i = 1;
        while(!Q.empty()){
            TreeNode* tmp = Q.front();
            Q.pop();
            if(StringList[i] != "NULL"){ // 重构左子结点
                TreeNode* left = new TreeNode(std::stoi(StringList[i]));
                Q.push(left);
                tmp->left = left;
            } 
            // 省略 else 因为默认就是NULL
            i++;

            if(StringList[i] != "NULL"){ // 重构右子结点
                TreeNode* right = new TreeNode(std::stoi(StringList[i]));
                Q.push(right);
                tmp->right = right;
            } 
            // 省略 else 因为默认就是NULL
            i++;
        }
        return head;
    }
};

int main(int argc, char *argv[]){
    TreeNode* Node1 = new TreeNode(1);
    TreeNode* Node2 = new TreeNode(2);
    TreeNode* Node3 = new TreeNode(3);
    TreeNode* Node4 = new TreeNode(4);
    TreeNode* Node5 = new TreeNode(5);

    Node1->left = Node2;
    Node1->right = Node3;
    Node3->left = Node4;
    Node3->right = Node5;

    Codec S1;
    // 序列化
    std::string test = S1.serialize(Node1);
    std::cout << test << std::endl;

    // 反序列化
    TreeNode* root = S1.deserialize(test);
    std::queue<TreeNode* > Q;
    Q.push(root);
    std::cout << root->val << " ";
    while(!Q.empty()){
        TreeNode* tmp = Q.front();
        Q.pop();
        if(tmp->left != NULL){
            Q.push(tmp->left);
            std::cout << tmp->left->val << " ";
        }
        else std::cout << "NULL ";
        if(tmp->right != NULL){
            Q.push(tmp->right);
            std::cout << tmp->right->val << " ";
        }
        else std::cout << "NULL ";
    }
    return 0;
}

10--字符串的排列(38)

 主要思路:

        考察去重的全排列,因为字符串的字符可能会重复;全排列讲解参考视频:全排列讲解

        为了让相同字符相邻,首先对字符串进行排序;

        通过一个 visited 数组记录哪个字符已被访问,每次重头访问字符,遇到已访问的字符则跳过;

        通过剪枝同一树层的相同字符来减少搜索次数;

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

class Solution {
public:
    std::vector<std::string> permutation(std::string s) {
        visited.resize(s.size());
        std::sort(s.begin(), s.end()); // 对字符进行排序,保证相同字符相邻
        backtrack(s, path, 0);
        return Res;
    }
    void backtrack(std::string s, std::string &path, int index){
        if(index == s.size()){
            Res.push_back(path);
            return;
        }
        for(int i = 0; i < s.size(); i++){
            if(i > 0 && s[i-1] == s[i] && visited[i-1] == false) continue; // 同一树层,需要剪枝
            if(visited[i] == true) continue; // 该字符已被访问过
            path.push_back(s[i]);
            visited[i] = true;
            backtrack(s, path, index+1);
            // 回溯
            path.pop_back();
            visited[i] = false;
        }
    }
private:
    std::vector<std::string> Res; // 返回结果
    std::string path; // 记录字符串
    std::vector<bool> visited; // 记录已访问的结点
};

int main(int argc, char *argv[]){
    std::string s = "abc";
    Solution S1;
    std::vector<std::string> Res = S1.permutation(s);
    for(std::string item : Res){
        std::cout << item << std::endl;
    }
    return 0;
}

11--数组中出现次数超过一半的数字(39)

主要思路:

        利用哈希表,数字作为 key,数字的值作为 value,记录最大的 value 即可,返回对应的 key;

#include <iostream>
#include <vector>
#include <map>

class Solution {
public:
    int majorityElement(std::vector<int>& nums) {
        for(int num : nums){
            hash[num] += 1;
            if(hash[num] > max){
                max = hash[num];
                Res = num;
            }
        }
        return Res;
    }
private:
    std::map<int, int> hash;
    int max = 0;
    int Res = -1;
};

int main(int argc, char *argv[]){
    std::vector<int> test = {1, 2, 3, 2, 2, 2, 5, 4, 2};
    Solution S1;
    int Res = S1.majorityElement(test);
    std::cout << Res << std::endl;
    return 0;
}

12--最小的 k 个数(40)

主要思路:

        维护一个堆,堆中元素一开始从大到小排列,堆中存储 k 个数;

        之后遍历剩余的数字,每次与堆中最大的数值进行比较,并交换;

        最后返回堆中的 k 个数即可;

#include <iostream>
#include <vector>
#include <queue>

class Solution {
public:
    std::vector<int> getLeastNumbers(std::vector<int>& arr, int k) {
        if (k == 0) return Res;
        for(int i = 0; i < k; i++){ // 堆一开始存储 k 个数
            Stack.push(arr[i]);
        }
        for(int i = k; i < arr.size(); i++){ // 从第 k 个数开始比较,更新堆中的元素
            if(arr[i] < Stack.top()){
                Stack.pop();
                Stack.push(arr[i]);
            }
        }
        // 比较完毕后将堆中的元素存储在结果数组中
        for(int i = 0; i < k; i++){
            Res.push_back(Stack.top());
            Stack.pop();
        }
        return Res;
    }
private:
    std::priority_queue<int> Stack; // 维护的堆,priority queue默认数值大的优先级高,即数值大的在队头
    std::vector<int> Res;
};

int main(int argc, char *argv[]){
    std::vector<int> test = {3,2,1};
    int k = 2;
    Solution S1;
    std::vector<int> Res = S1.getLeastNumbers(test, k);
    for(int item : Res){
        std::cout << item << " ";
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值