返回

腾讯音乐常考题:剖析热门算法,直击面试痛点

后端

攻克腾讯音乐面试:深入剖析高频考题

腾讯音乐,互联网音乐巨头,其面试难度一直备受求职者关注。深入了解腾讯音乐常考题,对于求职者成功入职至关重要。本文将深入剖析腾讯音乐各事业部高频考题,提供切实可行的解题思路,助力求职者在面试中脱颖而出。

环形链表 II

题目 给定一个链表,找出并返回环形链表的入口节点。如果链表不存在环,返回 null。

解题思路:

使用哈希表或双指针法。哈希表法将遍历过的节点存储起来,当再次遇到相同节点时即表示存在环。双指针法则利用慢指针和快指针,慢指针每次前进一步,快指针每次前进两步。当快指针追上慢指针时,表示存在环,此时慢指针从头结点重新开始,和快指针同步前进,相遇点即为环形链表的入口。

代码示例(双指针法):

bool hasCycle(ListNode *head) {
  ListNode *slow = head, *fast = head;
  while (fast && fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) return true;
  }
  return false;
}

ListNode *detectCycle(ListNode *head) {
  if (!hasCycle(head)) return nullptr;
  ListNode *slow = head, *fast = head;
  while (fast && fast->next) {
    slow = slow->next;
    fast = fast->next->next;
  }
  slow = head;
  while (slow != fast) {
    slow = slow->next;
    fast = fast->next;
  }
  return slow;
}

验证 IP 地址

题目: 给定一个字符串,验证其是否为有效的 IP 地址。

解题思路:

将字符串分割成四个部分,逐一判断每个部分是否满足 IP 地址的格式要求。注意,IPv4 地址中每个部分的取值范围为 0-255,IPv6 地址中每个部分的长度不超过 4 个十六进制数字。

代码示例:

bool isValidIP(string ip) {
  vector<string> parts;
  split(ip, '.', parts);
  if (parts.size() != 4) return false;
  for (string &part : parts) {
    if (part.empty() || part.size() > 4) return false;
    for (char c : part) {
      if (!isdigit(c)) return false;
    }
    int num = stoi(part);
    if (num < 0 || num > 255) return false;
  }
  return true;
}

实现 Trie (前缀树)

题目: 实现一个 Trie (前缀树) 数据结构,支持以下操作:

  • 插入一个字符串
  • 查找一个字符串是否存在
  • 查找所有以某一前缀开头的字符串

解题思路:

Trie 是一个树形数据结构,每个节点代表一个字符。从根节点开始,依次将字符串中的字符插入到相应的子节点中。查找一个字符串时,从根节点开始依次查找,直到找到该字符串的最后一个字符所在的节点。查找所有以某一前缀开头的字符串时,从该前缀的最后一个字符所在的节点开始,遍历其所有子节点。

代码示例:

class Trie {
public:
  Trie() : root(new TrieNode()) {}

  void insert(string &word) {
    TrieNode *node = root;
    for (char c : word) {
      if (!node->children.count(c)) {
        node->children[c] = new TrieNode();
      }
      node = node->children[c];
    }
    node->isWord = true;
  }

  bool search(string &word) {
    TrieNode *node = root;
    for (char c : word) {
      if (!node->children.count(c)) {
        return false;
      }
      node = node->children[c];
    }
    return node->isWord;
  }

  vector<string> startsWith(string &prefix) {
    vector<string> result;
    TrieNode *node = root;
    for (char c : prefix) {
      if (!node->children.count(c)) {
        return result;
      }
      node = node->children[c];
    }
    dfs(node, prefix, result);
    return result;
  }

private:
  struct TrieNode {
    unordered_map<char, TrieNode *> children;
    bool isWord = false;
  };

  TrieNode *root;

  void dfs(TrieNode *node, string &prefix, vector<string> &result) {
    if (node->isWord) result.push_back(prefix);
    for (auto &[c, child] : node->children) {
      prefix.push_back(c);
      dfs(child, prefix, result);
      prefix.pop_back();
    }
  }
};

网络延迟时间

题目描述: 给定一个网络,每个节点有一个延迟值。求出从一个源节点到其他所有节点的最短延迟时间。

解题思路:

使用 Dijkstra 算法。该算法从源节点开始,逐步扩展到其他节点,并更新节点的最短延迟时间。算法的关键在于使用一个优先队列,将待处理的节点按其最短延迟时间排序,每次从队列中取出最短延迟时间的节点进行扩展。

代码示例:

vector<int> networkDelayTime(vector<vector<int>> &edges, int N, int K) {
  unordered_map<int, vector<pair<int, int>>> graph;
  for (vector<int> &edge : edges) {
    graph[edge[0]].push_back({edge[1], edge[2]});
  }

  vector<int> dist(N + 1, INT_MAX);
  dist[K] = 0;

  priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
  pq.push({0, K});

  while (!pq.empty()) {
    int d = pq.top().first;
    int node = pq.top().second;
    pq.pop();

    if (d > dist[node]) continue;

    for (pair<int, int> &neighbor : graph[node]) {
      int nextNode = neighbor.first;
      int nextDist = d + neighbor.second;
      if (nextDist < dist[nextNode]) {
        dist[nextNode] = nextDist;
        pq.push({nextDist, nextNode});
      }
    }
  }

  vector<int> result;
  for (int i = 1; i <= N; i++) {
    if (dist[i] == INT_MAX) {
      result.push_back(-1);
    } else {
      result.push_back(dist[i]);
    }
  }
  return result;
}

合并 K 个排序链表

题目描述: 合并 K 个已排序的链表为一个新的排序链表。

解题思路:

可以使用分治法或优先队列法。分治法将链表递归地分成较小的部分,然后合并较小的部分。优先队列法则将 K 个链表的头部节点加入优先队列,每次取出优先队列中的最小节点,并将其下一个节点加入优先队列,直到所有节点都处理完。

代码示例(优先队列法):

ListNode *mergeKLists(vector<ListNode *> &lists) {
  if (lists.empty()) return nullptr;

  ListNode *dummy = new ListNode(0);
  ListNode *curr = dummy;

  priority_queue<ListNode *, vector<ListNode *>, greater<ListNode *>> pq;
  for (ListNode *head : lists) {
    if (head) pq.push(head);
  }

  while (!pq.empty()) {
    ListNode *top = pq.top();
    pq.pop();
    curr->next = top;
    curr = curr->next;
    if (top->next) pq.push(top->next);
  }

  return dummy->next;
}

反转链表

题目描述: 给定一个链表,反转其链表。

解题思路:

可以使用递归或迭代法。递归法通过反转子链表来实现链表的反转。迭代法则使用三个指针,一个指向当前节点,一个指向其前一个节点,一个指向其后一个节点。通过指针的移动和交换