C++八股文(数据结构2)

1. 如何实现动态规划?

动态规划核心思想:将复杂问题分解为子问题,保存子问题结果避免重复计算

经典例子:斐波那契数列

// 递归(指数时间)
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

// DP自底向上(O(n)时间,O(n)空间)
int fibDP(int n) {
    if (n <= 1) return n;
    vector<int> dp(n + 1);
    dp[0] = 0, dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
    }
    return dp[n];
}

// 空间优化(O(1)空间)
int fibOptimized(int n) {
    if (n <= 1) return n;
    int prev2 = 0, prev1 = 1;
    for (int i = 2; i <= n; i++) {
        int curr = prev1 + prev2;
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

  • 四个步骤:①定义状态(dp数组含义)②状态转移方程 ③初始化边界 ④确定计算顺序
  • 两种实现:自顶向下(记忆化递归)、自底向上(迭代)
  • 优化技巧:滚动数组降维、状态压缩
  • 经典问题:最长公共子序列、最长递增子序列、编辑距离、股票买卖

2. 如何解决背包问题?

0-1背包(每个物品只能选一次)

// 物品i,容量j,dp[i][j]表示前i个物品容量j的最大价值
int knapsack(vector<int>& weights, vector<int>& values, int capacity) {
    int n = weights.size();
    vector<vector<int>> dp(n + 1, vector<int>(capacity + 1, 0));
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= capacity; j++) {
            if (weights[i-1] <= j) {
                // 选或不选第i个物品
                dp[i][j] = max(dp[i-1][j], 
                              dp[i-1][j-weights[i-1]] + values[i-1]);
            } else {
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    return dp[n][capacity];
}

// 空间优化:一维数组(倒序遍历)
int knapsackOptimized(vector<int>& weights, vector<int>& values, int capacity) {
    vector<int> dp(capacity + 1, 0);
    for (int i = 0; i < weights.size(); i++) {
        for (int j = capacity; j >= weights[i]; j--) {
            dp[j] = max(dp[j], dp[j - weights[i]] + values[i]);
        }
    }
    return dp[capacity];
}

  • 完全背包:物品可无限选,内层循环正序遍历
  • 多重背包:每个物品有数量限制,转化为0-1背包
  • 时间复杂度:O(n×capacity)
  • 应用:资源分配、投资组合、切割问题

3. 如何实现 KMP 算法?

// 构建next数组(部分匹配表)
vector<int> buildNext(string pattern) {
    int m = pattern.length();
    vector<int> next(m, 0);
    int j = 0;  // 前缀长度
    for (int i = 1; i < m; i++) {
        while (j > 0 && pattern[i] != pattern[j]) {
            j = next[j - 1];  // 回退
        }
        if (pattern[i] == pattern[j]) {
            j++;
        }
        next[i] = j;
    }
    return next;
}

// KMP匹配
int kmpSearch(string text, string pattern) {
    int n = text.length(), m = pattern.length();
    if (m == 0) return 0;
    
    vector<int> next = buildNext(pattern);
    int j = 0;  // pattern的索引
    
    for (int i = 0; i < n; i++) {
        while (j > 0 && text[i] != pattern[j]) {
            j = next[j - 1];  // 不匹配时回退
        }
        if (text[i] == pattern[j]) {
            j++;
        }
        if (j == m) {
            return i - m + 1;  // 找到匹配
        }
    }
    return -1;  // 未找到
}

  • 核心思想:利用已匹配信息,避免重复比较
  • next数组:记录模式串中最长相同前后缀长度
  • 时间复杂度:O(n+m),优于暴力的O(n×m)
  • 应用:字符串搜索、文本编辑器、DNA序列匹配

4. 什么是二分查找?如何实现?

// 标准二分查找(有序数组)
int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;  // 防溢出
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;  // 未找到
}

// 查找第一个>=target的位置(lower_bound)
int lowerBound(vector<int>& arr, int target) {
    int left = 0, right = arr.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

// 查找第一个>target的位置(upper_bound)
int upperBound(vector<int>& arr, int target) {
    int left = 0, right = arr.size();
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

  • 前提条件:数组有序
  • 时间复杂度:O(log n)
  • 边界处理:left<=right还是left<right,mid+1还是mid
  • STL函数:binary_search、lower_bound、upper_bound
  • 变体:旋转数组、峰值查找、平方根

5. 如何找到二叉树的最低公共祖先(LCA)?

// 递归解法
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (!root || root == p || root == q) return root;
    
    TreeNode* left = lowestCommonAncestor(root->left, p, q);
    TreeNode* right = lowestCommonAncestor(root->right, p, q);
    
    if (left && right) return root;  // p和q分别在左右子树
    return left ? left : right;  // 都在一侧
}

// BST的LCA(利用有序性)
TreeNode* lcaBST(TreeNode* root, TreeNode* p, TreeNode* q) {
    while (root) {
        if (p->val < root->val && q->val < root->val) {
            root = root->left;  // 都在左子树
        } else if (p->val > root->val && q->val > root->val) {
            root = root->right;  // 都在右子树
        } else {
            return root;  // 分叉点就是LCA
        }
    }
    return nullptr;
}

  • 思路:如果p和q分别在左右子树,当前节点就是LCA;否则LCA在有p或q的那一侧
  • 时间复杂度:O(n)
  • BST优化:利用有序性,O(h)时间
  • 变体:多个节点的LCA、带父指针的树

6. 如何实现堆排序?

// 调整堆(大顶堆)
void heapify(vector<int>& arr, int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    
    if (left < n && arr[left] > arr[largest])
        largest = left;
    if (right < n && arr[right] > arr[largest])
        largest = right;
    
    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, n, largest);  // 递归调整
    }
}

// 堆排序
void heapSort(vector<int>& arr) {
    int n = arr.size();
    
    // 建堆:从最后一个非叶子节点开始
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }
    
    // 排序:依次取出堆顶元素
    for (int i = n - 1; i > 0; i--) {
        swap(arr[0], arr[i]);  // 堆顶(最大值)放到末尾
        heapify(arr, i, 0);    // 调整剩余元素
    }
}

  • 堆性质:完全二叉树,父节点>=子节点(大顶堆)或<=(小顶堆)
  • 时间复杂度:O(n log n),建堆O(n),每次调整O(log n)
  • 空间复杂度:O(1),原地排序
  • 不稳定排序:相同元素相对位置可能改变
  • STL:priority_queue、make_heap、push_heap、pop_heap

7. 如何使用哈希表来解决重复问题?

// 判断数组是否有重复元素
bool containsDuplicate(vector<int>& nums) {
    unordered_set<int> seen;
    for (int num : nums) {
        if (seen.count(num)) return true;
        seen.insert(num);
    }
    return false;
}

// 两数之和
vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int, int> map;  // 值->索引
    for (int i = 0; i < nums.size(); i++) {
        int complement = target - nums[i];
        if (map.count(complement)) {
            return {map[complement], i};
        }
        map[nums[i]] = i;
    }
    return {};
}

// 字符串中第一个不重复的字符
int firstUniqChar(string s) {
    unordered_map<char, int> count;
    for (char c : s) count[c]++;
    for (int i = 0; i < s.length(); i++) {
        if (count[s[i]] == 1) return i;
    }
    return -1;
}

  • 常见应用:去重、计数、查找配对、缓存
  • 时间复杂度:平均O(1)查找和插入
  • 空间换时间:用额外空间换取时间效率
  • STL容器:unordered_set、unordered_map

8. 如何在 C++ 中实现并查集?

class UnionFind {
    vector<int> parent;
    vector<int> rank;  // 秩(树的高度)
    
public:
    UnionFind(int n) : parent(n), rank(n, 0) {
        for (int i = 0; i < n; i++) {
            parent[i] = i;  // 初始每个元素是自己的父节点
        }
    }
    
    // 查找根节点(路径压缩)
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 路径压缩
        }
        return parent[x];
    }
    
    // 合并两个集合(按秩合并)
    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX != rootY) {
            if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else {
                parent[rootY] = rootX;
                rank[rootX]++;
            }
        }
    }
    
    // 判断是否在同一集合
    bool connected(int x, int y) {
        return find(x) == find(y);
    }
};

  • 核心操作:find(查找)、union(合并)
  • 优化技巧:①路径压缩(find时将节点直接连到根)②按秩合并(矮树连到高树)
  • 时间复杂度:接近O(1)(反阿克曼函数)
  • 应用:连通性问题、最小生成树(Kruskal)、朋友圈、岛屿数量

9. 如何使用 STL 实现有序容器?

// set:有序集合,不允许重复
set<int> s = {3, 1, 4, 1, 5};  // 自动排序去重:{1,3,4,5}
s.insert(2);
s.erase(3);
if (s.count(4)) cout << "存在";
auto it = s.lower_bound(3);  // 第一个>=3的元素

// multiset:允许重复
multiset<int> ms = {1, 2, 2, 3};
ms.erase(ms.find(2));  // 只删除一个2

// map:有序键值对
map<string, int> m;
m["apple"] = 1;
m.insert({"banana", 2});
for (auto& [key, val] : m) {  // 按key有序遍历
    cout << key << ":" << val;
}

// multimap:key可重复
multimap<int, string> mm;
mm.insert({1, "one"});
mm.insert({1, "uno"});

  • 底层实现:红黑树(平衡二叉搜索树)
  • 时间复杂度:插入、删除、查找都是O(log n)
  • 有序性:按key自动排序,可自定义比较函数
  • 迭代器稳定:插入删除不影响其他迭代器
  • 与unordered对比:有序但慢,unordered无序但快

10. 如何使用 C++ 实现二分查找树?

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

class BST {
    TreeNode* root;
    
    // 插入(递归)
    TreeNode* insert(TreeNode* node, int val) {
        if (!node) return new TreeNode(val);
        if (val < node->val) {
            node->left = insert(node->left, val);
        } else if (val > node->val) {
            node->right = insert(node->right, val);
        }
        return node;
    }
    
    // 查找
    TreeNode* search(TreeNode* node, int val) {
        if (!node || node->val == val) return node;
        if (val < node->val) return search(node->left, val);
        return search(node->right, val);
    }
    
    // 删除
    TreeNode* deleteNode(TreeNode* node, int val) {
        if (!node) return nullptr;
        if (val < node->val) {
            node->left = deleteNode(node->left, val);
        } else if (val > node->val) {
            node->right = deleteNode(node->right, val);
        } else {
            // 找到要删除的节点
            if (!node->left) return node->right;
            if (!node->right) return node->left;
            // 两个子节点:找右子树最小值替换
            TreeNode* minNode = findMin(node->right);
            node->val = minNode->val;
            node->right = deleteNode(node->right, minNode->val);
        }
        return node;
    }
    
    TreeNode* findMin(TreeNode* node) {
        while (node->left) node = node->left;
        return node;
    }
    
public:
    BST() : root(nullptr) {}
    void insert(int val) { root = insert(root, val); }
    bool search(int val) { return search(root, val) != nullptr; }
    void remove(int val) { root = deleteNode(root, val); }
};

  • BST性质:左子树<根<右子树,中序遍历得到有序序列
  • 时间复杂度:平均O(log n),最坏O(n)(退化成链表)
  • 平衡树:AVL树、红黑树保证O(log n)
  • 应用:动态有序数据、范围查询、STL的set/map底层

全部评论

相关推荐

牛客嘻嘻子:建议可以把mentor布置的任务和AI沟通一下
点赞 评论 收藏
分享
01-11 08:47
门头沟学院 Java
羊村你懒哥1:如果不放毕业,我只能说导师是自己选的,错在你选了个垃圾导师,不在你实习
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务