哈希表查找逻辑
哈希表的底层是桶数组(一个存放键值对的数组)
- 先用 key 通过哈希函数计算出哈希值
- 再根据哈希值和桶(bucket)数量确定索引位置
- 若该位置的 key 与目标一致,则返回对应 value
- 否则按冲突解决策略(如链地址法或开放寻址)继续查找,直到匹配 key 并取出 value。
- 元素的占到容量的百分比是负载因子(默认 0.75),超过了阈值则扩容
- HashMap 的扩容是扩大二倍,原来的哈希值会新增 1 个 bit,既不用重新计算又能随机分散到新的桶中
滑动窗口
left 是左边界的索引,是在窗口内的最左元素
定长窗口直接算
1
| int left = right - k + 1;//k 是窗口长度
|
不定长用 while
1 2 3 4 5 6 7
| //这个是典型,left初值是0,给0出边界后left进一 //此时left就指向新窗口的左边界了 //窗口长度就是正常的右减左加一,right-left+1 while (n0 > k) { if (nums[left] == 0) n0--; left++; }
|
二分查找
- 查找 target 第一次出现的索引
- ranges::lower_bound C++20 里引入了这个函数,找下界,下面这就是他的具体实现
- 默认比较器用的 less,就是 mid 值 < target,改用 greater 后就是 mid > target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int n = nums.size(); int left = 0, right = n - 1;//指针初值设为两头 // 循环不变量:整个循环包括结束都会严格满足下面这两个条件 // 循环不变量:看最后左右指针在mid汇合的时候的条件,计算之后左右指针落点 // nums[left-1] < target // nums[right+1] >= target // 因为是二分查找,所以最后两指针是相邻的 while ( left <= right ) {//while循环 int mid = (left + right ) / 2; //如果中间值在target左侧,就更新left,缩小左边界 if (nums[mid] < target) {//这个地方比较的是mid,老错写成left //left要设为mid+1,是闭区间最左边界 left = mid + 1; } //right要设为mid-1 else right = mid - 1; } return left;//最后返回的是target第一次出现的索引,也等于right+1 //也适用于target不存在的情况,只要记住循环不变量就行 //就比如lower_bound(nums, 0);能返回第一个0的索引 //lower_bound(nums,1);若1不存在能返回第一个大于0的索引
|
lower_bound(nums.begin(),nums.(end),target),升序,找到第一个>=target 的值的元素,并返回该元素的迭代器
lower_bound( begin , end , val , less<type>() )升序,找到第一个>=target 的元素,与上面不写的升序功能相同,官方的自定义比较函数
lower_bound( begin , end , val , greater<type>() )降序,找到第一个<=target 的元素
- 返回的值是迭代器,若要获取索引,需要用
- begin
ranges::lower_bound(nums2.begin(),nums2.end(), nums1[i], ranges::greater{}) - nums2.begin();//减去开始获取索引
upper_bound()也是升序,但是是找到第一个>target 的元素
- 寻找两个正序数组的中位数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| class Solution { public: double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) { if (nums1.size() > nums2.size()) { swap(nums1, nums2); } int n1 = nums1.size(); int n2 = nums2.size(); nums1.insert(nums1.begin(), INT_MIN); nums2.insert(nums2.begin(), INT_MIN); nums1.push_back(INT_MAX); nums2.push_back(INT_MAX);
int left = 0; int right = n1; while (left <= right) { int mid = left + (right - left) / 2; int i = mid; int j = (n1 + n2 + 1)/2 - i; if (nums1[i] <= nums2[j + 1]) { left = mid + 1; } else { right = mid - 1; } } int i = right; int j = (n1 + n2 + 1)/2 - i; int first = max(nums1[i], nums2[j]); int second = min(nums1[i + 1], nums2[j + 1]); return (n1 + n2)%2 ? first : (first + second)/2.0; } };
|
前缀和
s[i]就是前 i 个元素的和
适合求区间和,无论是否有序
1 2 3 4 5 6 7
| vector<int> s; s.resize(nums.size() + 1); for (int i = 0; i < nums.size(); i++) { s[i + 1] = s[i] + nums[i]; }
|
找所有和为 k 的子串
1 2 3 4 5 6 7
| for (int i = 0 ; i < pre.size(); i++) { auto it = map.find(pre[i] - k); if (it != nullptr) ans += it -> second; map[pre[i]]++; }
|
链表 List
翻转链表
1 2 3 4 5 6 7 8 9 10
| ListNode* reverseList(ListNode* node) { ListNode* pre = nullptr, *cur = node; while (cur != nullptr) { ListNode* nxt = cur -> next; cur -> next = pre; pre = cur; cur = nxt; } return pre; }
|
快慢指针法,查看是否存在环型链表
1 2 3 4 5 6 7 8 9 10 11 12
| bool hasCycle(ListNode *head) { ListNode* fast = head, *slow = head; while (fast && fast -> next) { slow = slow -> next; fast = fast -> next -> next; if (fast == slow) return true; } return false; }
|
快慢指针法,找中间节点
1 2 3 4 5 6 7 8
| ListNode* middleNode(ListNode* head) { ListNode* slow = head, *fast = head; while (fast != nullptr && fast -> next != nullptr) { slow = slow -> next; fast = fast -> next -> next; } return slow; }
|
虚拟头结点,头插法,合并两个有序链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode dummy(0); ListNode* tail = &dummy; while (list1 != nullptr && list2 != nullptr) { if (list1 -> val <= list2 -> val) { tail -> next = list1; list1 = list1 -> next; } else { tail -> next = list2; list2 = list2 -> next; } tail = tail -> next; } tail -> next = list1 ? list1 : list2; return dummy.next; }
|
二叉树
深度优先搜索(DFS),用递归或者栈实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void Recursion(TreeNode* root, vector<int> &ans) { if (root == nullptr) return; Recursion(root->left, ans); ans.push_back(root->val); Recursion(root->right, ans); }
stack<TreeNode*> stk;
while (root != nullptr || !stk.empty()) { while (root != nullptr) { stk.push(root); root = root->left; } root = stk.top(); ans.push_back(stk.top()->val); stk.pop(); root = root->right; }
|
广度优先搜索(BFS),用队列实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
queue<TreeNode*> queue;
vector<vector<int>> res; queue.push(root); while (!queue.empty()) { vector<int> vals; for(int n = queue.size(); n > 0; n--) { TreeNode* node = queue.front(); if (node->left) queue.push(node->left); if (node->right) queue.push(node->right); vals.push_back(node->val); queue.pop(); } res.emplace_back(vals); } return res;
|
递归
把原问题化解成一个个和相似的子问题,然后确定好递归的终止条件
数组转换为平衡二叉树
1 2 3 4 5 6 7 8 9 10 11
| // 把 nums[left] 到 nums[right-1] 转成平衡二叉搜索树 TreeNode* dfs(vector<int>& nums, int left, int right) { if (left == right) return nullptr; int m = (left + right) / 2; TreeNode* node = new TreeNode(nums[m]); node->left = dfs(nums, left, m); node->right = dfs(nums, m+1 , right); return node; // 也可以直接用构造函数写返回值,第一个参数是val,第二个是左子树,第二个是右子树 // return new TreeNode(m, dfs(nums, left, m), dfs(nums, m+1, right)) }
|
二叉搜索树,左子树严格小于根节点,右子树严格大于根节点
中序遍历就是升序
回溯
用递归实现
全排列问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Solution { public: vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> ans; int n = nums.size(); vector<int> vec(n), select(n);
auto dfs = [&](this auto&& dfs, int i) -> void { if (i == n) {位数到n的时候停止 ans.emplace_back(vec); return; } for (int j = 0; j < n; j++) { if (select[j] == 0) { vec[i] = nums[j]; select[j] = 1; dfs(i + 1); select[j] = 0; } } }; dfs(0); return ans; } };
|
问:什么时候需要写恢复现场,什么时候不需要写?
答:下面代码中,如果初始化 path 为空列表,就需要写恢复现场。本题由于所有括号长度都是固定的 2n,我们可以创建一个长为 2n 的 path 列表,在递归时直接写入字符(而不是插入字符),这样做无需写恢复现场。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Solution { public: vector<string> generateParenthesis(int n) { vector<string> ans; string path; auto dfs = [&](this auto&& dfs, int l, int r) { if (r == n) { ans.emplace_back(path); return; } if (l < n) { path.push_back('('); dfs(l + 1, r); path.pop_back(); } if (l > r) { path.push_back(')'); dfs(l, r + 1); path.pop_back(); } return; }; dfs(0, 0); return ans; } };
string path(2 * n, 0);
path[l + r] = '('; dfs(l + 1, r);
|
79.单词搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class Solution { public: bool exist(vector<vector<char>>& board, string word) { bool flag = false; int n = word.size(); vector<vector<int>> mark(board.size(), vector<int>(board[0].size(), 0));
auto dfs = [&](this auto&& dfs, int i, int x, int y) -> void { if (x<0 || y<0 || x>=board.size() || y>=board[0].size()) return; if (board[x][y] != word[i] || mark[x][y] == 1) return; mark[x][y] = 1; if (i == n - 1) { flag = true; } dfs(i + 1, x + 1, y); dfs(i + 1, x, y + 1); dfs(i + 1, x - 1, y); dfs(i + 1, x, y - 1); mark[x][y] = 0; }; for (int x = 0; x < board.size(); x++) { for (int y = 0; y < board[0].size(); y++) { dfs(0, x, y); } } return flag; } };
|