推荐博客五大常用算法:分治、动态规划、贪心、回溯和分支界定 刷题时注意边界条件/特殊条件的处理 leetcode记录1
树的子结构输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
思路: 由于是树相关的题目,故大致思路就是使用递归解决,也意识到需要借助辅助函数实现,但一直无法确定辅助函数的写法与用法。后面看到题解才知道咋写,记录在此。
一篇文章带你吃透对称性递归(思路分析+解题模板+案例解读)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public : bool hasSubStructure (TreeNode *A, TreeNode *B) { if (B == nullptr ) { return true ; } if (A == nullptr || A->val != B->val) { return false ; } return hasSubStructure (A->left, B->left) && hasSubStructure (A->right, B->right); } bool isSubStructure (TreeNode *A, TreeNode *B) { if (A == nullptr || B == nullptr ) { return false ; } return hasSubStructure (A, B) || isSubStructure (A->left, B) || isSubStructure (A->right, B); } };
CPP
前 K 个高频元素给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
思路: 使用哈希表统计出现次数,使用快排划分的方式找到出现频率最高的前k个元素
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 class Solution { public : int partition (vector<pair<int , int >>& arr, int start, int end) { int p = arr[end].second; int slow = start - 1 ; for (int fast = start; fast < end; fast++) { if (arr[fast].second >= p) { slow++; swap (arr[slow], arr[fast]); } } slow++; swap (arr[slow], arr[end]); return slow; } void getTopK (vector<pair<int , int >>& arr, int start, int end, int k) { int index = partition (arr, start, end); if (index > k - 1 ) { getTopK (arr, start, index - 1 , k); } if (index < k - 1 ) { getTopK (arr, index + 1 , end, k); } } vector<int > topKFrequent (vector<int >& nums, int k) { map<int , int > cnt; for (int num : nums) { cnt[num]++; } vector<pair<int , int >> items (cnt.begin (), cnt.end ()); getTopK (items, 0 , items.size () - 1 , k); vector<int > ans; for (int i = 0 ; i < k; i++) { ans.emplace_back (items[i].first); } return ans; } };
CPP
看了看题解,可以使用小顶堆(优先队列)的方式维持前k大的数据
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 : static bool cmp (pair<int , int >& m, pair<int , int >& n) { return m.second > n.second; } vector<int > topKFrequent (vector<int >& nums, int k) { unordered_map<int , int > occurrences; for (auto & v : nums) { occurrences[v]++; } priority_queue<pair<int , int >, vector<pair<int , int >>, decltype (&cmp)> q (cmp); for (auto & [num, count] : occurrences) { if (q.size () == k) { if (q.top ().second < count) { q.pop (); q.emplace (num, count); } } else { q.emplace (num, count); } } vector<int > ret; while (!q.empty ()) { ret.emplace_back (q.top ().first); q.pop (); } return ret; } };
CPP
二叉树的最近公共祖先给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
思路: 我的思路是计算根节点到两节点的路径,然后取其公共的部分,由根节点执行公共路径,得到最近公共祖先
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 47 48 49 50 class Solution { public : bool findNode (TreeNode* node, TreeNode* target, string& path) { if (node == nullptr ) { return false ; } if (node == target) { return true ; } if (findNode (node->left, target, path)) { path.push_back ('l' ); return true ; } if (findNode (node->right, target, path)) { path.push_back ('r' ); return true ; } return false ; } TreeNode* lowestCommonAncestor (TreeNode* root, TreeNode* p, TreeNode* q) { string path1 = "" ; string path2 = "" ; findNode (root, p, path1); findNode (root, q, path2); int min_len = min (path1.length (), path2.length ()); reverse (path1.begin (), path1.end ()); reverse (path2.begin (), path2.end ()); int i; for (i = 0 ; i < min_len; i++) { if (path1[i] != path2[i]) { break ; } } TreeNode* res = root; for (int j = 0 ; j < i; j++) { if (path1[j] == 'l' ) { res = res->left; } else { res = res->right; } } return res; } };
CPP
官方题解的方法比较巧妙,但不怎么容易想到
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 class Solution { public : bool existTargetNode (TreeNode* root, TreeNode* p, TreeNode* q) { if (find_ancestor) { return false ; } if (root == nullptr ) { return false ; } bool lres = existTargetNode (root->left, p, q); bool rres = existTargetNode (root->right, p, q); if ((lres && rres) || (root == p || root == q) && (lres || rres)) { find_ancestor = true ; ans = root; } return root == p || root == q || lres || rres; } TreeNode* lowestCommonAncestor (TreeNode* root, TreeNode* p, TreeNode* q) { ans = nullptr ; find_ancestor = false ; existTargetNode (root, p, q); return ans; } private : TreeNode* ans; bool find_ancestor; };
CPP
岛屿数量给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网格的四条边均被水包围。
思路: 刚开始我的思路就是使用dfs计算连通分量,但当时我只访问两个方向,因为我觉得这样可以省去一些遍历,但忘了这个就是通过计算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 26 27 28 29 30 31 32 33 34 class Solution { public : void dfs (vector<vector<char >>& grid, vector<vector<bool >>& visited, int x, int y) { visited[x][y] = true ; if (x + 1 < grid.size () && grid[x + 1 ][y] == '1' && visited[x + 1 ][y] == false ) { dfs (grid, visited, x + 1 , y); } if (y + 1 < grid[0 ].size () && grid[x][y + 1 ] == '1' && visited[x][y + 1 ] == false ) { dfs (grid, visited, x, y + 1 ); } } int numIslands (vector<vector<char >>& grid) { int m = grid.size (); int n = grid[0 ].size (); vector<vector<bool >> visited (m, vector <bool >(n, false )); int ans = 0 ; for (int i = 0 ; i < m; i++) { for (int j = 0 ; j < n; j++) { if (grid[i][j] == '1' && visited[i][j] == false ) { dfs (grid, visited, i, j); ans++; } } } return ans; } };
CPP
正确方法:
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 class Solution { public : void dfs (vector<vector<char >>& grid, vector<vector<bool >>& visited, int x, int y) { visited[x][y] = true ; static const int dir[4 ][2 ] = {{0 , 1 }, {0 , -1 }, {1 , 0 }, {-1 , 0 }}; for (int i = 0 ; i < 4 ; i++) { int nx = x + dir[i][0 ]; int ny = y + dir[i][1 ]; if (nx >= 0 && nx < grid.size () && ny >= 0 && ny < grid[0 ].size () && grid[nx][ny] == '1' && visited[nx][ny] == false ) { dfs (grid, visited, nx, ny); } } } int numIslands (vector<vector<char >>& grid) { int m = grid.size (); int n = grid[0 ].size (); vector<vector<bool >> visited (m, vector <bool >(n, false )); int ans = 0 ; for (int i = 0 ; i < m; i++) { for (int j = 0 ; j < n; j++) { if (grid[i][j] == '1' && visited[i][j] == false ) { dfs (grid, visited, i, j); ans++; } } } return ans; } };
CPP
最长回文子串给你一个字符串 s,找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
思路 我的思路就是动态规划,两层遍历一遍即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <bits/stdc++.h> using namespace std;class Solution { public : string longestPalindrome (string s) { int len = s.length (); vector<vector<bool >> dp (len, vector <bool >(len, true )); int string_len = 1 ; int string_start = 0 ; for (int sub_len = 2 ; sub_len <= len; sub_len++) { for (int start = 0 ; start <= len - sub_len; start++) { if (s[start] == s[start + sub_len - 1 ] && dp[start + 1 ][start + sub_len - 2 ] == true ) { dp[start][start + sub_len - 1 ] = true ; string_len = sub_len; string_start = start; } else { dp[start][start + sub_len - 1 ] = false ; } } } return s.substr (string_start, string_len); } };
CPP
官方题解采用的是中心扩散的方式,由一个字符或两个字符向两边扩散
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 class Solution { public : pair<int , int > expandBound (const string& s, int start, int end) { while (s[start] == s[end]) { start--; end++; } return {start + 1 , end - start - 1 }; } string longestPalindrome (string s) { s = "+" + s + "-" ; int string_start = 0 ; int string_len = 0 ; for (int i = 1 ; i < s.length () - 1 ; i++) { auto [start1, len1] = expandBound (s, i, i); auto [start2, len2] = expandBound (s, i, i + 1 ); if (len1 > string_len) { string_start = start1; string_len = len1; } if (len2 > string_len) { string_start = start2; string_len = len2; } } return s.substr (string_start, string_len); } };
CPP
二叉树的层序遍历给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
思路 使用队列实现层序遍历,我的实现是为节点添加层级信息,用于区分各层节点
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 class Solution { public : using Node = pair<TreeNode *, int >; vector<vector<int >> levelOrder (TreeNode *root) { if (root == nullptr ) { return {}; } vector<vector<int >> ans; queue<Node> qu; int curr_level = -1 ; qu.emplace (root, 0 ); while (!qu.empty ()) { auto [node, level] = qu.front (); qu.pop (); if (curr_level != level) { ans.emplace_back (vector<int >{node->val}); curr_level = level; } else { ans[level].emplace_back (node->val); } if (node->left != nullptr ) { qu.emplace (node->left, level + 1 ); } if (node->right != nullptr ) { qu.emplace (node->right, level + 1 ); } } return ans; } };
CPP
官方题解直接按层级对节点进行操作,更加简洁一点
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 class Solution { public : vector<vector<int >> levelOrder (TreeNode *root) { if (root == nullptr ) { return {}; } vector<vector<int >> ans; queue<TreeNode *> qu; int curr_level = -1 ; qu.emplace (root); while (!qu.empty ()) { ans.emplace_back (vector<int >{}); int level_size = qu.size (); for (int i = 0 ; i < level_size; i++) { TreeNode *node = qu.front (); qu.pop (); ans.back ().emplace_back (node->val); if (node->left != nullptr ) { qu.emplace (node->left); } if (node->right != nullptr ) { qu.emplace (node->right); } } } return ans; } };
CPP
最长公共子序列给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
思路 经典的动态规划问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> using namespace std;class Solution { public : int longestCommonSubsequence (string text1, string text2) { int len1 = text1.length (); int len2 = text2.length (); vector<vector<int >> dp (len1 + 1 , vector <int >(len2 + 1 )); for (int i = 1 ; i <= len1; i++) { for (int j = 1 ; j <= len2; j++) { int max_len = 0 ; if (text1[i - 1 ] == text2[j - 1 ]) { max_len = dp[i - 1 ][j - 1 ] + 1 ; } dp[i][j] = max ({max_len, dp[i - 1 ][j], dp[i][j - 1 ]}); } } return dp[len1][len2]; } };
CPP
K 个一组翻转链表给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。 k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
思路 虽然指针操作有点复杂,但对着样例调整指针操作也不是很难,使用虚拟头节点避免对头部节点的特殊处理
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 #include <bits/stdc++.h> using namespace std;struct ListNode { int val; ListNode* next; ListNode () : val (0 ), next (nullptr ) {} ListNode (int x) : val (x), next (nullptr ) {} ListNode (int x, ListNode* next) : val (x), next (next) {} };class Solution { public : ListNode* reverseKGroup (ListNode* head, int k) { if (k == 1 ) { return head; } ListNode virt_head (-1 , head) ; ListNode* prev_node = &virt_head; ListNode* next_node = head; while (true ) { for (int i = 0 ; i < k; i++) { if (next_node == nullptr ) { return virt_head.next; } next_node = next_node->next; } ListNode* prev = prev_node->next; ListNode* curr = prev_node->next->next; ListNode* next; for (int i = 0 ; i < k - 1 ; i++) { next = curr->next; curr->next = prev; prev = curr; curr = next; } prev_node->next->next = next; ListNode* tmp = prev_node->next; prev_node->next = prev; prev_node = tmp; } return virt_head.next; } };
CPP
子集给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
思路 以0-2^n^-1的数字表示各个元素选择情况,n为数组长度,数字对应位为1则选择该位置元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <bits/stdc++.h> using namespace std;class Solution { public : vector<vector<int >> subsets (vector<int >& nums) { vector<vector<int >> ans; int length = nums.size (); for (int i = 0 ; i < 1 << length; i++) { vector<int > tmp; for (int j = 0 ; j < length; j++) { if ((i & (1 << j)) != 0 ) { tmp.emplace_back (nums[j]); } } ans.emplace_back (tmp); } return ans; } };
CPP
外观数列给定一个正整数 n ,输出外观数列的第 n 项。 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
思路 比较简单,依次遍历模拟即可
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 #include <bits/stdc++.h> using namespace std;class Solution { public : string countAndSay (int n) { string ans = "1" ; for (int i = 1 ; i < n; i++) { string tmp; ans.push_back ('\0' ); char last_char = '\0' ; int times = 0 ; for (char c : ans) { if (last_char == '\0' ) { last_char = c; times = 1 ; } else if (c == last_char) { times++; } else { tmp.append (to_string (times)); tmp.push_back (last_char); last_char = c; times = 1 ; } } ans = move (tmp); } return ans; } };
CPP
合并区间以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
思路 我的思路是将起点终点都放到数组中进行比较,记录当前经过的起点与终点数,若相等则将区间起点与终点加入结果集合,实现上起点附加值设为-1,终点附加值设为1,保证附加值为0时起点数与终点数相等,且起点与终点值相同时,起点在终点前面。
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 #include <bits/stdc++.h> using namespace std;class Solution { public : using Node = pair<int , int >; vector<vector<int >> merge (vector<vector<int >>& intervals) { vector<Node> numbers; for (auto & vec : intervals) { numbers.emplace_back (vec[0 ], -1 ); numbers.emplace_back (vec[1 ], 1 ); } sort (numbers.begin (), numbers.end ()); vector<vector<int >> ans; int start = -1 ; int cnt = 0 ; for (auto & node : numbers) { if (cnt == 0 ) { start = node.first; } cnt += node.second; if (cnt == 0 ) { ans.emplace_back (vector<int >{start, node.first}); } } return ans; } };
CPP
官方题解的思路更容易理解一点,按起点排序,持续扩大终点边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> using namespace std;class Solution { public : vector<vector<int >> merge (vector<vector<int >>& intervals) { vector<vector<int >> ans; auto cmp = [](const vector<int >& v1, const vector<int >& v2) -> bool { return v1[0 ] < v2[0 ]; }; sort (intervals.begin (), intervals.end (), cmp); ans.emplace_back (intervals[0 ]); for (int i = 1 ; i < intervals.size (); i++) { auto & last = ans.back (); if (intervals[i][0 ] > last[1 ]) { ans.emplace_back (intervals[i]); } else { last[1 ] = max (last[1 ], intervals[i][1 ]); } } return ans; } };
CPP
括号生成数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
思路 刚开始我的思路是计算出n-1的所有括号对数,然后在前后各加一次括号,在两边加一次括号
1 2 f1: () f2: f1 + () () + f1 ( + f1 + )
C
但发现n=4的时候结果就出错了,比对一下发现(())(())没有在结果集合中,f4不能全部由f3推导而来,也可以从f2+f2推导而来。 故换一种思路,枚举fi+fn-i,最后加上( + fn-1 + )
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 #include <bits/stdc++.h> using namespace std;class Solution { public : vector<string> generateParenthesis (int n) { vector<vector<string>> ans (n + 1 ); ans[1 ].emplace_back ("()" ); for (int i = 2 ; i <= n; i++) { unordered_set<string> tmp; for (string& str : ans[i - 1 ]) { tmp.emplace ("(" + str + ")" ); } for (int j = 1 ; j < i; j++) { for (string& str1 : ans[j]) { for (string& str2 : ans[i - j]) { tmp.emplace (str1 + str2); } } } ans[i] = vector <string>(tmp.begin (), tmp.end ()); } return ans[n]; } };
CPP
删除链表的倒数第 N 个结点给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
思路 链表中非常常见的两个思路:虚拟头节点与双指针 虚拟头节点一般用来避免对头节点的特殊处理,双指针利用指针的行进速度,位置关系巧妙地解决问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public : ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode virt_head (-1 , head) ; ListNode* slow = &virt_head; ListNode* fast = &virt_head; for (int i = 0 ; i < n + 1 ; i++) { fast = fast->next; } while (fast != nullptr ) { slow = slow->next; fast = fast->next; } slow->next = slow->next->next; return virt_head.next; } };
CPP
基数排序十大排序算法(背诵版+动图) 排序(基数排序 牛客网提交地址) 笔试选择题中天天考基数排序,就简单实现一下加深一下印象
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 #include <bits/stdc++.h> using namespace std;class Solution { public : vector<int > sortArray (vector<int >& nums) { int max_num = *max_element (nums.begin (), nums.end ()); int bits = 0 ; while (max_num != 0 ) { max_num /= 10 ; bits++; } vector<vector<int >> bucket (10 ); for (int i = 0 ; i < bits; i++) { for (int num : nums) { int index = num / static_cast <int >(pow (10 , i)) % 10 ; bucket[index].emplace_back (num); } int k = 0 ; for (auto & items : bucket) { for (int num : items) { nums[k++] = num; } items.clear (); } } return nums; } };int main () { int n; while (cin >> n) { vector<int > data (n) ; for (int i = 0 ; i < n; i++) { cin >> data[i]; } Solution solution; auto res = solution.sortArray (data); for (int num : res) { cout << num << " " ; } cout << endl; } }
CPP
二分查找 正经解法写对二分查找不是套模板并往里面填空,需要仔细分析题意
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 #include <bits/stdc++.h> using namespace std;int BinaryFindEqual (const vector<int >& data, int target) { int low = 0 ; int high = data.size () - 1 ; while (low < high) { int mid = (low + high) / 2 ; if (data[mid] == target) { return mid; } else if (data[mid] > target) { high = mid - 1 ; } else { low = mid + 1 ; } } if (data[low] == target) { return low; } return -1 ; }int BinaryFindFirstGreaterEqual (const vector<int >& data, int target) { int low = 0 ; int high = data.size (); while (low < high) { int mid = (low + high) / 2 ; if (data[mid] >= target) { high = mid; } else { low = mid + 1 ; } } return low; }int BinaryFindFirstGreater (const vector<int >& data, int target) { int low = 0 ; int high = data.size (); while (low < high) { int mid = (low + high) / 2 ; if (data[mid] > target) { high = mid; } else { low = mid + 1 ; } } return low; }int BinaryFindLastLesserEqual (const vector<int >& data, int target) { if (data[0 ] > target) { return -1 ; } int low = 0 ; int high = data.size () - 1 ; while (low < high) { int mid = (low + high + 1 ) / 2 ; if (data[mid] > target) { high = mid - 1 ; } else { low = mid; } } return low; }int BinaryFindLastLesser (const vector<int >& data, int target) { if (data[0 ] >= target) { return -1 ; } int low = 0 ; int high = data.size () - 1 ; while (low < high) { int mid = (low + high + 1 ) / 2 ; if (data[mid] >= target) { high = mid - 1 ; } else { low = mid; } } return low; }int BinaryFindFirstEqual (const vector<int >& data, int target) { int low = 0 ; int high = data.size () - 1 ; while (low < high) { int mid = (low + high) / 2 ; if (data[mid] > target) { high = mid - 1 ; } else if (data[mid] < target) { low = mid + 1 ; } else { high = mid; } } if (data[low] == target) { return low; } return -1 ; }int BinaryFindLastEqual (const vector<int >& data, int target) { int low = 0 ; int high = data.size () - 1 ; while (low < high) { int mid = (low + high + 1 ) / 2 ; if (data[mid] > target) { high = mid - 1 ; } else if (data[mid] < target) { low = mid + 1 ; } else { low = mid; } } if (data[low] == target) { return low; } return -1 ; }int BinaryFindEqualCompare (const vector<int >& data, int target) { for (int i = 0 ; i < data.size (); i++) { if (data[i] == target) { return i; } } return -1 ; }int BinaryFindFirstGreaterEqualCompare (const vector<int >& data, int target) { for (int i = 0 ; i < data.size (); i++) { if (data[i] >= target) { return i; } } return data.size (); }int BinaryFindFirstGreaterCompare (const vector<int >& data, int target) { for (int i = 0 ; i < data.size (); i++) { if (data[i] > target) { return i; } } return data.size (); }int BinaryFindLastLesserEqualCompare (const vector<int >& data, int target) { for (int i = data.size () - 1 ; i >= 0 ; i--) { if (data[i] <= target) { return i; } } return -1 ; }int BinaryFindLastLesserCompare (const vector<int >& data, int target) { for (int i = data.size () - 1 ; i >= 0 ; i--) { if (data[i] < target) { return i; } } return -1 ; }int BinaryFindFirstEqualCompare (const vector<int >& data, int target) { for (int i = 0 ; i < data.size (); i++) { if (data[i] == target) { return i; } } return -1 ; }int BinaryFindLastEqualCompare (const vector<int >& data, int target) { for (int i = data.size () - 1 ; i >= 0 ; i--) { if (data[i] == target) { return i; } } return -1 ; }using FindFunc = function<int (const vector<int >&, int )>;void TestBinaryFind (const vector<int >& data, const vector<int >& targets, FindFunc test_fn, FindFunc right_fn, string testname) { for (int target : targets) { int res1 = test_fn (data, target); int res2 = right_fn (data, target); if (res1 != res2) { cout << "wrong anwer." << endl; cout << "res1: " << res1 << " res2: " << res2 << endl; } } cout << testname << " complete." << endl; }int main () { vector<int > unique_data; default_random_engine e; uniform_int_distribution<int > u (1 , 100 ) ; e.seed (time (0 )); for (int i = 5 ; i < 95 ; i++) { if (u (e) > 50 ) { unique_data.emplace_back (i); } } vector<int > targets; for (int i = 0 ; i <= 100 ; i++) { targets.emplace_back (i); } cout << "unique data test:" << endl; TestBinaryFind (unique_data, targets, BinaryFindEqual, BinaryFindEqualCompare, "BinaryFindEqual" ); TestBinaryFind (unique_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual" ); TestBinaryFind (unique_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater" ); TestBinaryFind (unique_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual" ); TestBinaryFind (unique_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser" ); vector<int > repeat_data; for (int i = 5 ; i < 95 ; i++) { while (u (e) > 30 ) { repeat_data.emplace_back (i); } } cout << "repeat data test:" << endl; TestBinaryFind (repeat_data, targets, BinaryFindFirstGreaterEqual, BinaryFindFirstGreaterEqualCompare, "BinaryFindFirstGreaterEqual" ); TestBinaryFind (repeat_data, targets, BinaryFindFirstGreater, BinaryFindFirstGreaterCompare, "BinaryFindFirstGreater" ); TestBinaryFind (repeat_data, targets, BinaryFindLastLesserEqual, BinaryFindLastLesserEqualCompare, "BinaryFindLastLesserEqual" ); TestBinaryFind (repeat_data, targets, BinaryFindLastLesser, BinaryFindLastLesserCompare, "BinaryFindLastLesser" ); TestBinaryFind (repeat_data, targets, BinaryFindFirstEqual, BinaryFindFirstEqualCompare, "BinaryFindFirstEqual" ); TestBinaryFind (repeat_data, targets, BinaryFindLastEqual, BinaryFindLastEqualCompare, "BinaryFindLastEqual" ); }
CPP
取巧解法相关leetcode题目 34. 在排序数组中查找元素的第一个和最后一个位置 35. 搜索插入位置 704. 二分查找
本来是比较基础的题目,面试时问到卡壳了,故在此记录一下二分查找变形的简单解法
在排序数组中查找元素的第一个和最后一个位置 给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target,返回 [-1, -1]。 你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
思路 显然解题并不需要实现两个函数,只需要找到任意一个等于然后向两边扩散即可,但我为了熟悉二分查找的变形,实现了两个函数,分别用来求第一个等于和最后一个等于。这其中就使用了一个技巧,对前一个或后一个值进行额外判断,这样就能将范围查找规约成单个满足条件的值的查找 ,这样就能避免一系列的死循环和边界条件了
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 #include <bits/stdc++.h> using namespace std;class Solution { public : int find_first_equal (vector<int >& nums, int target) { int low = 0 ; int high = nums.size () - 1 ; while (low <= high) { int mid = (low + high) / 2 ; if (nums[mid] > target) { high = mid - 1 ; } else if (nums[mid] < target) { low = mid + 1 ; } else { if (mid == 0 || nums[mid - 1 ] != target) { return mid; } high = mid - 1 ; } } return -1 ; } int find_last_equal (vector<int >& nums, int target) { int low = 0 ; int high = nums.size () - 1 ; while (low <= high) { int mid = (low + high) / 2 ; if (nums[mid] > target) { high = mid - 1 ; } else if (nums[mid] < target) { low = mid + 1 ; } else { if (mid == nums.size () - 1 || nums[mid + 1 ] != target) { return mid; } low = mid + 1 ; } } return -1 ; } vector<int > searchRange (vector<int >& nums, int target) { int first = find_first_equal (nums, target); int second = find_last_equal (nums, target); return {first, second}; } };
CPP
搜索插入位置 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。
思路 使用上述技巧即可,判断前一个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <bits/stdc++.h> using namespace std;class Solution { public : int searchInsert (vector<int >& nums, int target) { int low = 0 ; int high = nums.size () - 1 ; while (low <= high) { int mid = (low + high) / 2 ; if (nums[mid] < target) { low = mid + 1 ; } else { if (mid == 0 || nums[mid - 1 ] < target) { return mid; } high = mid - 1 ; } } return nums.size (); } };
CPP
二分查找 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <bits/stdc++.h> using namespace std;class Solution { public : int search (vector<int >& nums, int target) { int low = 0 ; int high = nums.size () - 1 ; while (low <= high) { int mid = (low + high) / 2 ; if (nums[mid] > target) { high = mid - 1 ; } else if (nums[mid] < target) { low = mid + 1 ; } else { return mid; } } return -1 ; } };
CPP
完成所有任务的最少时间你有一台电脑,它可以 同时 运行无数个任务。给你一个二维整数数组 tasks ,其中 tasks[i] = [starti, endi, durationi] 表示第 i 个任务需要在 闭区间 时间段 [starti, endi] 内运行 durationi 个整数时间点(但不需要连续)。 当电脑需要运行任务时,你可以打开电脑,如果空闲时,你可以将电脑关闭。 请你返回完成所有任务的情况下,电脑最少需要运行多少秒。
思路: 周赛的第四题,没思路,动态规划写多了,想不到贪心,而且这个非常像差分数组,但实际又不能用,就放弃了 参考别人的题解:两种方法:暴力/线段树(Python/Java/C++/Go)
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 class Solution { public : int findMinimumTime (vector<vector<int >>& tasks) { auto cmp = [](const vector<int >& task1, const vector<int >& task2) { return task1[1 ] < task2[1 ]; }; sort (tasks.begin (), tasks.end (), cmp); vector<int > run (2001 , 0 ) ; int ans = 0 ; for (auto & task : tasks) { int start = task[0 ]; int end = task[1 ]; int duration = task[2 ]; for (int i = start; i <= end; i++) { duration -= run[i]; } for (int i = end; duration > 0 ; i--) { if (run[i] == 0 ) { run[i] = 1 ; duration--; ans++; } } } return ans; } };
CPP
统计美丽子数组数目给你一个下标从 0 开始的整数数组nums 。每次操作中,你可以:
选择两个满足 0 <= i, j < nums.length 的不同下标 i 和 j 。 选择一个非负整数 k ,满足 nums[i] 和 nums[j] 在二进制下的第 k 位(下标编号从 0 开始)是 1 。 将 nums[i] 和 nums[j] 都减去 2k 。 如果一个子数组内执行上述操作若干次后,该子数组可以变成一个全为 0 的数组,那么我们称它是一个 美丽 的子数组。
请你返回数组 nums 中 美丽子数组 的数目。 子数组是一个数组中一段连续 非空 的元素序列。
思路: 周赛的第三题,使用两层循环枚举超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public : long long beautifulSubarrays (vector<int >& nums) { vector<int > dp = nums; long long ans = 0 ; for (int num : nums) { if (num == 0 ) { ans++; } } for (int len = 2 ; len <= nums.size (); len++) { for (int end = nums.size () - 1 ; end >= len - 1 ; end--) { dp[end] = dp[end - 1 ] ^ nums[end]; if (dp[end] == 0 ) { ans++; } } } return ans; } };
CPP
而题解使用了前缀和与异或的性质【套路】前缀和+哈希表(Python/Java/C++/Go)
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 class Solution { public : long long beautifulSubarrays (vector<int >& nums) { int n = nums.size (); vector<int > prefix (n + 1 , 0 ) ; for (int i = 0 ; i < n; i++) { prefix[i + 1 ] = prefix[i] ^ nums[i]; } long long ans = 0 ; unordered_map<int , int > cnt; for (int s : prefix) { ans += cnt[s]; cnt[s]++; } return ans; } };
CPP
航班预订统计这里有 n 个航班,它们分别从 1 到 n 进行编号。 有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。 请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。
思路: 差分数组模板题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public : vector<int > corpFlightBookings (vector<vector<int >>& bookings, int n) { vector<int > ans (n, 0 ) ; for (auto & booking : bookings) { ans[booking[0 ] - 1 ] += booking[2 ]; if (booking[1 ] < n) { ans[booking[1 ]] -= booking[2 ]; } } for (int i = 1 ; i < n; i++) { ans[i] += ans[i - 1 ]; } return ans; } };
CPP
两数之和给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。
硬套双指针做法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public : vector<int > twoSum (vector<int >& nums, int target) { vector<pair<int , int >> index (nums.size ()); for (int i = 0 ; i < nums.size (); i++) { index[i] = {nums[i], i}; } sort (index.begin (), index.end ()); int i = 0 ; int j = nums.size () - 1 ; while (i < j) { if (index[i].first + index[j].first == target) { return {index[i].second, index[j].second}; } else if (index[i].first + index[j].first > target) { j--; } else { i++; } } return {}; } };
CPP
使用map加速查找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public : vector<int > twoSum (vector<int >& nums, int target) { unordered_map<int , int > hash_map; for (int i = 0 ; i < nums.size (); i++) { auto iter = hash_map.find (target - nums[i]); if (iter != hash_map.end ()) { return {iter->second, i}; } hash_map[nums[i]] = i; } return {}; } };
CPP
三数之和给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请 你返回所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
直接使用set去重,使用最简单的两数之和方法
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 class Solution { public : vector<vector<int >> threeSum (vector<int >& nums) { if (nums.size () < 3 ) { return {}; } sort (nums.begin (), nums.end ()); set<vector<int >> se; for (int i = 0 ; i < nums.size () - 2 ; i++) { int target = -nums[i]; int j = i + 1 ; int k = nums.size () - 1 ; while (j < k) { if (nums[j] + nums[k] == target) { se.emplace (vector<int >{nums[i], nums[j], nums[k]}); j++; k--; } else if (nums[j] + nums[k] > target) { k--; } else { j++; } } } return vector<vector<int >>(se.begin (), se.end ()); } };
CPP
逻辑去重,跳过相同字符
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 class Solution { public : vector<vector<int >> threeSum (vector<int >& nums) { if (nums.size () < 3 ) { return {}; } sort (nums.begin (), nums.end ()); vector<vector<int >> ans; int i = 0 ; while (i < nums.size () - 2 ) { int target = -nums[i]; int j = i + 1 ; int k = nums.size () - 1 ; while (j < k) { if (nums[j] + nums[k] == target) { ans.emplace_back (vector<int >{nums[i], nums[j], nums[k]}); int value_j = nums[j]; int vlaue_k = nums[k]; while (j < nums.size () && nums[j] == value_j) { j++; } while (k >= 0 && nums[k] == vlaue_k) { k--; } } else if (nums[j] + nums[k] > target) { k--; } else { j++; } } while (i < nums.size () && nums[i] == -target) { i++; } } return ans; } };
CPP
连接所有点的最小费用给你一个points 数组,表示 2D 平面上的一些点,其中 points[i] = [xi, yi] 。 连接点 [xi, yi] 和点 [xj, yj] 的费用为它们之间的 曼哈顿距离 :|xi - xj| + |yi - yj| ,其中 |val| 表示 val 的绝对值。 请你返回将所有点连接的最小总费用。只有任意两点之间 有且仅有 一条简单路径时,才认为所有点都已连接。
最小生成树模板题
还是用visit数组标记是否已加入集合比较好,注意优先队列自定义比较函数的方式
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 const int kInf = 1 << 30 ;class Solution { public : int minCostConnectPoints (vector<vector<int >>& points) { int n = points.size (); vector<int > dist (n, kInf) ; dist[0 ] = 0 ; auto cmp = [](const pair<int , int >& p1, const pair<int , int >& p2) -> bool { return p1.second > p2.second; }; priority_queue<pair<int , int >, vector<pair<int , int >>, decltype (cmp)> qu (cmp); vector<bool > visit (n, false ) ; qu.emplace (0 , 0 ); while (!qu.empty ()) { auto [node, node_dist] = qu.top (); qu.pop (); if (visit[node]) { continue ; } visit[node] = true ; int x1 = points[node][0 ]; int y1 = points[node][1 ]; for (int i = 0 ; i < n; i++) { if (i != node && !visit[i]) { int x2 = points[i][0 ]; int y2 = points[i][1 ]; int manhan_dist = abs (x1 - x2) + abs (y1 - y2); if (dist[i] > manhan_dist) { dist[i] = manhan_dist; qu.emplace (i, manhan_dist); } } } } return accumulate (dist.begin (), dist.end (), 0 ); } };
CPP
省份数量有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。 省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。 给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。 返回矩阵中 省份 的数量。思路: union-find模题,单纯用来熟悉union-find算法
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 class Solution { public : int findOperation (int k) { if (vec_[k] == k) { return k; } vec_[k] = findOperation (vec_[k]); return vec_[k]; } void unionOperation (int p, int q) { int proot = findOperation (p); int qroot = findOperation (q); vec_[proot] = qroot; } int findCircleNum (vector<vector<int >>& isConnected) { int n = isConnected.size (); vec_ = vector <int >(n); for (int i = 0 ; i < n; i++) { vec_[i] = i; } for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n; j++) { if (isConnected[i][j] == 1 ) { unionOperation (i, j); } } } int cnt = 0 ; for (int i = 0 ; i < n; i++) { if (vec_[i] == i) { cnt++; } } return cnt; } private : vector<int > vec_; };
CPP
概率最大的路径给你一个由 n 个节点(下标从 0 开始)组成的无向加权图,该图由一个描述边的列表组成,其中 edges[i] = [a, b] 表示连接节点 a 和 b 的一条无向边,且该边遍历成功的概率为 succProb[i] 。 指定两个节点分别作为起点 start 和终点 end ,请你找出从起点到终点成功概率最大的路径,并返回其成功概率。 如果不存在从 start 到 end 的路径,请 返回 0 。只要答案与标准答案的误差不超过 1e-5 ,就会被视作正确答案。思路: Dijkstra算法变种大多采用不同的松弛操作,若需要优化算法性能,可使用优先队列 第一个版本:不使用优先队列,超时,中间一个bug是max_dist使用了int
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 struct Edge { int to; double prob; };class Solution { public : double maxProbability (int n, vector<vector<int >>& edges, vector<double >& succProb, int start, int end) { vector<vector<Edge>> mat (n); for (int i = 0 ; i < edges.size (); i++) { mat[edges[i][0 ]].emplace_back (Edge{edges[i][1 ], succProb[i]}); mat[edges[i][1 ]].emplace_back (Edge{edges[i][0 ], succProb[i]}); } vector<bool > visit (n, false ) ; vector<double > dist (n, 0 ) ; dist[start] = 1 ; int node = start; visit[node] = true ; for (int times = 0 ; times < n; times++) { for (Edge e : mat[node]) { if (dist[e.to] < dist[node] * e.prob) { dist[e.to] = dist[node] * e.prob; } } double max_dist = 0 ; int max_index = -1 ; for (int i = 0 ; i < n; i++) { if (!visit[i] && dist[i] > max_dist) { max_dist = dist[i]; max_index = i; } } if (max_index == end) { return dist[end]; } if (max_index == -1 ) { return 0 ; } visit[max_index] = true ; node = max_index; } return visit[end]; } };
CPP
第二个版本:借助优先队列,但仍然使用了标记数组
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 struct Edge { int to; double prob; bool operator <(const Edge& e) { return prob < e.prob; } };class Solution { public : double maxProbability (int n, vector<vector<int >>& edges, vector<double >& succProb, int start, int end) { vector<vector<Edge>> mat (n); for (int i = 0 ; i < edges.size (); i++) { mat[edges[i][0 ]].emplace_back (Edge{edges[i][1 ], succProb[i]}); mat[edges[i][1 ]].emplace_back (Edge{edges[i][0 ], succProb[i]}); } vector<bool > visit (n, false ) ; vector<double > dist (n, 0 ) ; priority_queue<pair<double , int >> qu; dist[start] = 1 ; qu.emplace (1.0 , start); while (!qu.empty ()) { auto [prob, node] = qu.top (); qu.pop (); if (node == end) { return dist[end]; } if (visit[node]) { continue ; } visit[node] = true ; for (Edge e : mat[node]) { if (dist[e.to] < dist[node] * e.prob) { dist[e.to] = dist[node] * e.prob; qu.emplace (dist[e.to], e.to); } } } return visit[end]; } };
CPP
第三个版本:借助优先队列,但不需要标记数组,可以多看几遍
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 : double maxProbability (int n, vector<vector<int >>& edges, vector<double >& succProb, int start, int end) { vector<vector<pair<int , double >>> mat (n); for (int i = 0 ; i < edges.size (); i++) { mat[edges[i][0 ]].emplace_back (edges[i][1 ], succProb[i]); mat[edges[i][1 ]].emplace_back (edges[i][0 ], succProb[i]); } priority_queue<pair<double , int >> qu; vector<double > dist (n, 0 ) ; dist[start] = 1 ; qu.emplace (1 , start); while (!qu.empty ()) { auto [prob, node] = qu.top (); qu.pop (); if (prob < dist[node]) { continue ; } if (node == end) { return dist[end]; } for (auto & [next_node, edge_prob] : mat[node]) { if (dist[next_node] < dist[node] * edge_prob) { dist[next_node] = dist[node] * edge_prob; qu.emplace (dist[next_node], next_node); } } } return dist[end]; } };
CPP
实现 Trie (前缀树)Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。 void insert(String word) 向前缀树中插入字符串 word 。 boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。 boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
思路: 了解了节点组成之后还是挺简单的,子节点指针数组与结束标志
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 class Trie { public : Trie () { memset (children_, 0 , sizeof (children_)); is_end_ = false ; } void insert (string word) { Trie* node = this ; for (char c : word) { if (node->children_[c - 'a' ] == nullptr ) { node->children_[c - 'a' ] = new Trie (); } node = node->children_[c - 'a' ]; } node->is_end_ = true ; } bool search (string word) { Trie* node = this ; for (char c : word) { if (node->children_[c - 'a' ] == nullptr ) { return false ; } node = node->children_[c - 'a' ]; } return node->is_end_; } bool startsWith (string prefix) { Trie* node = this ; for (char c : prefix) { if (node->children_[c - 'a' ] == nullptr ) { return false ; } node = node->children_[c - 'a' ]; } return true ; } private : Trie* children_[26 ]; bool is_end_; };
CPP
LRU 缓存请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。 函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
思路: 与15445的LRU类似,注意size的更新与淘汰操作
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 47 48 49 50 51 52 53 54 class LRUCache { public : LRUCache (int capacity) : capacity_ (capacity), size_ (0 ) {} int get (int key) { auto map_iter = speed_map_.find (key); if (map_iter == speed_map_.end ()) { return -1 ; } auto list_iter = map_iter->second; int value = list_iter->second; if (list_iter != data_.begin ()) { data_.erase (list_iter); data_.emplace_front (key, value); speed_map_[key] = data_.begin (); } return value; } void put (int key, int value) { auto map_iter = speed_map_.find (key); if (map_iter == speed_map_.end ()) { if (size_ == capacity_) { speed_map_.erase (data_.back ().first); data_.pop_back (); size_--; } data_.emplace_front (key, value); speed_map_.insert ({key, data_.begin ()}); size_++; } else { auto list_iter = map_iter->second; list_iter->second = value; if (list_iter != data_.begin ()) { data_.erase (list_iter); data_.emplace_front (key, value); speed_map_[key] = data_.begin (); } } } private : int capacity_; int size_; unordered_map<int , list<pair<int , int >>::iterator> speed_map_; list<pair<int , int >> data_; };
CPP
合并两个有序链表代码化简,while循环改成for循环
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 class Solution { public : ListNode* mergeTwoLists (ListNode* list1, ListNode* list2) { ListNode virt_head (-1 , nullptr ) ; ListNode* cur; for (cur = &virt_head; list1 != nullptr && list2 != nullptr ; cur = cur->next) { if (list1->val <= list2->val) { cur->next = list1; list1 = list1->next; } else { cur->next = list2; list2 = list2->next; } } if (list1 != nullptr ) { cur->next = list1; } if (list2 != nullptr ) { cur->next = list2; } return virt_head.next; } };
CPP
数组中的第 k 大的数字给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。思路: 利用快排的划分函数即可
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 class Solution { public : int partition (vector<int >& nums, int start, int end) { int p = nums[end]; int slow = start - 1 ; for (int fast = start; fast < end; fast++) { if (nums[fast] < p) { slow++; swap (nums[slow], nums[fast]); } } swap (nums[slow + 1 ], nums[end]); return slow + 1 ; } void sort (vector<int >& nums, int start, int end, int target) { if (start >= end) { return ; } int mid = partition (nums, start, end); if (mid > target) { sort (nums, start, mid - 1 , target); } else if (mid < target) { sort (nums, mid + 1 , end, target); } } int findKthLargest (vector<int >& nums, int k) { sort (nums, 0 , nums.size () - 1 , nums.size () - k); return nums[nums.size () - k]; } };
CPP
排序数组快排:以前都只看过双指针格式的代码,看了看leetcode单指针的代码,比较易懂
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 class Solution { public : int partition (vector<int >& nums, int start, int end) { int index = rand () % (end - start + 1 ) + start; swap (nums[index], nums[end]); int p = nums[end]; int slow = start - 1 ; for (int fast = start; fast < end; fast++) { if (nums[fast] < p) { slow++; swap (nums[slow], nums[fast]); } } swap (nums[slow + 1 ], nums[end]); return slow + 1 ; } void quickSort (vector<int >& nums, int start, int end) { if (end > start) { int mid = partition (nums, start, end); quickSort (nums, start, mid - 1 ); quickSort (nums, mid + 1 , end); } } vector<int > sortArray (vector<int >& nums) { quickSort (nums, 0 , nums.size () - 1 ); return nums; } };
CPP
归并:不怎么喜欢用夹带++i或i++的表达式,总觉得不好看,非递归的实现忘了
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 : void mergeSort (vector<int >& nums, int start, int end) { if (start >= end) { return ; } int mid = (start + end) / 2 ; mergeSort (nums, start, mid); mergeSort (nums, mid + 1 , end); int i = start; int j = mid + 1 ; int k = start; while (i <= mid && j <= end) { if (nums[i] <= nums[j]) { tmp_vec_[k] = nums[i]; i++; k++; } else { tmp_vec_[k] = nums[j]; j++; k++; } } while (i <= mid) { tmp_vec_[k] = nums[i]; i++; k++; } while (j <= end) { tmp_vec_[k] = nums[j]; j++; k++; } for (int i = start; i <= end; i++) { nums[i] = tmp_vec_[i]; } } vector<int > sortArray (vector<int >& nums) { tmp_vec_.resize (nums.size ()); mergeSort (nums, 0 , nums.size () - 1 ); return nums; } private : vector<int > tmp_vec_; };
CPP
堆排序,记住下沉操作即可
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 class Solution { public : void maxHeapify (vector<int >& nums, int i, int len) { int left = 2 * i + 1 ; int right = 2 * i + 2 ; int largest = i; if (left <= len && nums[left] > nums[largest]) { largest = left; } if (right <= len && nums[right] > nums[largest]) { largest = right; } if (largest != i) { swap (nums[i], nums[largest]); maxHeapify (nums, largest, len); } } void buildMaxHeap (vector<int >& nums, int len) { for (int i = len / 2 ; i >= 0 ; i--) { maxHeapify (nums, i, len); } } void heapSort (vector<int >& nums) { heap_size_ = nums.size () - 1 ; buildMaxHeap (nums, heap_size_); for (int i = nums.size () - 1 ; i > 0 ; i--) { swap (nums[i], nums[0 ]); heap_size_--; maxHeapify (nums, 0 , heap_size_); } } vector<int > sortArray (vector<int >& nums) { heapSort (nums); return nums; } private : int heap_size_; };
CPP
LABULADONG 动态规划专题LABULADONG 的算法网站
零钱兑换给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const int kMax = 1 << 28 ;class Solution { public : int coinChange (vector<int >& coins, int amount) { vector<int > dp (amount + 1 , kMax) ; dp[0 ] = 0 ; for (int i = 1 ; i <= amount; i++) { for (int coin : coins) { if (i - coin >= 0 ) { dp[i] = min (dp[i], dp[i - coin] + 1 ); } } } return dp[amount] == kMax ? -1 : dp[amount]; } };
CPP
变换次序,去掉条件判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const int kMax = 1 << 28 ;class Solution { public : int coinChange (vector<int >& coins, int amount) { vector<int > dp (amount + 1 , kMax) ; dp[0 ] = 0 ; for (int coin : coins) { for (int i=coin;i<=amount;i++){ dp[i] = min (dp[i], dp[i - coin] + 1 ); } } return dp[amount] == kMax ? -1 : dp[amount]; } };
CPP
最长递增子序列给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public : int lengthOfLIS (vector<int >& nums) { vector<int > dp (nums.size(), 1 ) ; int longest = 1 ; for (int i = 1 ; i < nums.size (); i++) { for (int j = 0 ; j < i; j++) { if (nums[j] < nums[i]) { dp[i] = max (dp[i], dp[j] + 1 ); } } longest = max (longest, dp[i]); } return longest; } };
CPP
分割等和子集给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路: 0-1背包模板题,若压缩空间,则需要逆序更新保证为dp数组上一次更新的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public : bool canPartition (vector<int >& nums) { int sum = accumulate (nums.begin (), nums.end (), 0 ); if (nums.size () <= 1 || sum % 2 == 1 ) { return false ; } int target = sum / 2 ; vector<bool > dp (target + 1 , false ) ; dp[0 ] = true ; for (int i = 0 ; i < nums.size (); i++) { for (int j = target; j >= nums[i]; j--) { dp[j] = dp[j] | dp[j - nums[i]]; } } return dp[target]; } };
CPP
零钱兑换 II给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带符号整数。
思路: 代码很简单,但通过调换次序避免重复却很巧妙 通过将coin设为主序,确定了内层循环的末尾硬币一定为当前coin,且组合序列一定遵从coins数组排序,即coin为1 2 5,则序列一定为1 1 2 之类的序列,而不会出现2 1 1的序列
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public : int change (int amount, vector<int >& coins) { vector<int > dp (amount + 1 , 0 ) ; dp[0 ] = 1 ; for (int coin : coins) { for (int i = coin; i <= amount; i++) { dp[i] += dp[i - coin]; } } return dp[amount]; } };
CPP