依照CodeTop后端算法题考查频度(并非全部)。
3.无重复字符的最长子串 滑动窗口: 维护一个unordered_map
类型的window
变量以统计当前窗口中各字符出现的次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int l = 0 , r = 0 ;while (r < s.size ()) { char c = s[r++]; window[c]++; while (window[c] > 1 ) { char d = s[l++]; window[d]--; } }
25.K个一组翻转链表 [1,2,3,4,5](k=2)
→ [2,1,4,3,5]
模拟翻转: 利用reverseK
翻转两个head
和tail
之间的k
个节点,并返回重置后的头尾,然后先统计链表节点的总数,k
个一组翻转即可。
1 2 3 4 5 6 7 8 9 10 11 12 pair<ListNode*, ListNode*> reverseK (ListNode* head, ListNode* tail) { ListNode* pre = tail->next; ListNode* cur = head; while (pre != tail) { ListNode* next = cur->next; cur->next = pre; pre = cur; cur = next; } return {tail, head}; }
215.数组中的第k个最大元素 快速选择: 快速排序的核心是“哨兵划分”和“递归”。
快速选择的思路是: 数组长度为N,如果某次哨兵划分后,基准数的索引正好是N - k,则它就是第k大的数字。然而,对于包含大量重复元素的数组,每轮的哨兵划分都可能将数组划分为1和n - 1两个部分,此时快排的时间复杂度会退化至O(N^2)
。
三路划分:每轮划分数组为三个部分,当发现第k大数字处在“等于基准数”的子数组中时,便可直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int quickSelect (vector<int >& nums, int k) { int pivot = nums[rand () % nums.size ()]; vector<int > big, equal, small; for (auto & num : nums) { if (num > pivot) big.emplace_back (num); else if (num < pivot) small.emplace_back (num); else equal.emplace_back (num); } if (k <= big.size ()) return quickSelect (big, k); if (k - big.size () > equal.size ()) return quickSelect (small, k - big.size () - equal.size ()); return pivot; }
103.二叉树的锯齿形层序遍历 奇数层翻转: 利用result.size() % 2 == 1
来判断该层是否为奇数层,若是奇数层,则用reverse
翻转。
1 2 3 4 if (res.size () % 2 == 1 ) reverse (tmp.begin (), tmp.end ()); res.emplace_back (tmp);
5.最长回文子串 中心扩展法: 子串不断向两边扩展,如果两边的字母相同就继续扩展。
1 2 3 4 5 6 7 8 9 string isPalding (string s, int left, int right) { while (left >= 0 && right < s.size () && s[left] == s[right]) { left--; right++; } return s.substr (left + 1 , right - left - 1 ); }
236.二叉树的最近公共祖先 1 2 3 4 5 6 7 8 TreeNode* lowestCommonAncestor (TreeNode* root, TreeNode* p, TreeNode* q) { if (root == p || root == q || root == nullptr ) return root; TreeNode* left = lowestCommonAncestor (root->left, p, q); TreeNode* right = lowestCommonAncestor (root->right, p, q); if (left != nullptr && right != nullptr ) return root; if (left == nullptr ) return right; return left; }
143.重排链表 若用vector
会带来O(N)
的空间复杂度。寻找链表中点 + 链表逆序 + 合并链表: 快慢指针先找到链表中点mid
,然后反转链表后半部分,最后合并即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void reorderList (ListNode* head) { if (head == nullptr ) return ; ListNode* mid = middleNode (head); ListNode* l1 = head; ListNode* l2 = mid->next; mid->next = nullptr ; l2 = reverseList (l2); mergeList (l1, l2); }
42.接雨水 双指针优化: 先用两个容器统计柱子左右两边最高的柱子,依次计算能接的雨水量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int trap (vector<int >& height) { int sum = 0 ; vector<int > maxLeft (height.size(), 0 ) ; vector<int > maxRight (height.size(), 0 ) ; maxLeft[0 ] = height[0 ]; maxRight[height.size () - 1 ] = height[height.size () - 1 ]; for (int i = 1 ; i < height.size (); ++i) maxLeft[i] = max (maxLeft[i - 1 ], height[i]); for (int i = height.size () - 2 ; i >= 0 ; --i) maxRight[i] = max (maxRight[i + 1 ], height[i]); for (int i = 1 ; i < height.size () - 1 ; ++i) { int temp = min (maxLeft[i], maxRight[i]) - height[i]; if (temp > 0 ) { sum += temp; } } return sum; }
54.螺旋矩阵 模拟遍历:
若matrix
为空,则直接返回空列表
初始化左、右、上、下四个边界l
r
t
b
按照题目要求的顺序循环遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 vector<int > result; int l = 0 , r = matrix[0 ].size () - 1 ;int t = 0 , b = matrix.size () - 1 ;while (true ) { for (int i = l; i <= r; ++i) result.emplace_back (matrix[t][i]); if (++t > b) break ; for (int i = t; i <= b; ++i) result.emplace_back (matrix[i][r]); if (--r < l) break ; for (int i = r; i >= l; --i) result.emplace_back (matrix[b][i]); if (--b < t) break ; for (int i = b; i >= t; --i) result.emplace_back (matrix[i][l]); if (++l > r) break ; }
82.删除排序链表中的重复元素II 核心: 利用辅助前序节点原地删除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ListNode* dummy = new ListNode (-1 ); dummy->next = head; ListNode* cur = dummy; while (cur->next && cur->next->next) { int val = cur->next->val; if (cur->next->next->val == val) { while (cur->next && cur->next->val == val) cur->next = cur->next->next; } else { cur = cur->next; } }
124.二叉树的最大路径和 核心:利用一个简化的函数maxGain(node)
计算二叉树中某一个节点的最大贡献值(以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大 )。
ex: [20, 15, 7]中,15和7的最大贡献值分别为15和7,而20的最大贡献值为35。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int maxGain (TreeNode* node) { if (node == nullptr ) return 0 ; int leftGain = max (maxGain (node->left), 0 ); int rightGain = max (maxGain (node->right), 0 ); int priceNewpath = node->val + leftGain + rightGain; maxSum = max (maxSum, priceNewpath); return node->val + max (leftGain, rightGain); }
31.下一个排列 思路: 将一个左边的[较小数]与右边的[较大数]交换,以能够让排列变大,从而得到下一个排列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void nextPermutation (vector<int >& nums) { int i = nums.size () - 2 ; while (i >= 0 && nums[i] >= nums[i + 1 ]) i--; if (i >= 0 ) { int j = nums.size () - 1 ; while (j >= 0 && nums[i] >= nums[j]) j--; swap (nums[i], nums[j]); } reverse (nums.begin () + i + 1 , nums.end ()); }