leetcode题目
有序列表转二叉查找树-109
Convert Sorted List to Binary Search Tree
二叉树的最小深度-111
Minimum Depth of Binary Tree
最小深度:根节点到某个叶子节点的最短路径。
为空,返回0
左孩子为空,则结果在右孩子
右孩子为空,则结果在左孩子
左右均不为空,返回小的+1
二叉树遍历
先序遍历-144
Binary Tree Preorder Traversal
根、左、右。栈 。
先把根节点入栈
栈不为空时,出栈一个元素
访问该元素,右孩子进栈,左孩子进栈 。 因为出栈,先出左孩子,再出右孩子。
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 vector <int > pre_order(TreeNode* root) { vector <int > vpre; stack <TreeNode*> st; if (root != nullptr ) { st.push(root); } while (!st.empty()) { TreeNode* p = st.top(); vpre.push_back(p->val); st.pop(); if (p->right) { st.push(p->right); } if (p->left) { st.push(p->left); } } return vpre; }
中序遍历-094
Binary Tree Inorder Traversal
思路
左、根、右。使用栈 。
p=root
p不为空,p入栈,一直向左走p = p.left
,扫描它的左孩子,所有左孩子依次入栈
p为空时,p = st.top()
,p位于栈顶 ,显然没有左孩子或者左孩子已经遍历过 ,p访问出栈 。
扫描右孩子 p = p.right
从根节点开始,一直向左,所有的左孩子入栈 , 出栈一个节点,访问 ,它的右孩子入栈 。
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 vector <int > inorder_traversal(TreeNode* root) { vector <int > res; stack <TreeNode*> st; TreeNode* p = root; while (p || !st.empty()) { if (p) { st.push(p); p = p->left; } else { p = st.top(); res.push_back(p->val); st.pop(); p = p->right; } } return res; }
后序遍历-145
Binary Tree Postorder Traversal
思路
左孩子、右孩子、根节点。使用栈。使用pre
记录上一次遍历的节点 。
根节点入栈
栈不为空,访问栈顶元素p
直接访问 p的条件:p没有左右孩子 or 左右孩子刚刚遍历结束 ,只要pre是左或者右孩子即可
p可以直接访问,则访问出栈
p不能直接访问,则左右孩子入栈
关键代码
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 vector <int > post_order(TreeNode* root) { vector <int > res; stack <TreeNode*> st; TreeNode* pre = nullptr ; if (root != nullptr ) { st.push(root); } while (!st.empty()) { TreeNode* p = st.top(); bool no_child = (p->left == nullptr && p->right == nullptr ); bool pre_is_child = (pre == p->left || pre == p->right); if (nullptr == pre) { pre_is_child = false ; } if (no_child || pre_is_child) { res.push_back(p->val); pre = p; st.pop(); } else { if (p->right) { st.push(p->right); } if (p->left) { st.push(p->left); } } } return res; }
层次遍历-102
层次遍历,使用队列。
从上到下 Binary Tree Level Order Traversal 和从下到上 Binary Tree Level Order Traversal II 。
如果只需要顺序放在一个数组里面,则不需要分层,直接层次遍历即可。
但是此题,需要分层构建vector。
数量记录思路
不是很好。
队列层次遍历
当前层在队列中的数量:cur_remain
下一层的数量:next_level
cur_remain == 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 28 29 30 31 32 33 34 35 36 37 38 vector <vector <int >> levelOrder(TreeNode* root) { vector <vector <int >> res; if (root == nullptr ) { return res; } queue <TreeNode*> q; q.push(root); res.push_back(vector <int >()); int cur_remain = 1 ; int next_level = 0 ; while (!q.empty()) { TreeNode* now = q.front(); q.pop(); res[res.size() - 1 ].push_back(now->val); if (now->left) { q.push(now->left); next_level++; } if (now->right) { q.push(now->right); next_level++; } cur_remain--; if (cur_remain == 0 && !q.empty()) { res.push_back(vector <int >()); cur_remain = next_level; next_level = 0 ; } } return res; }
一次遍历一层的思路
很好,掌握 !
一次while循环,保证当前队列里面只有当前层的元素 ,用vector记录当前层的序列
q.size()
获得当前层元素数量,然后本次循环,只从队列里面出这么多元素 。
依次遍历当前层的所有元素 ,出队,同时左右孩子入队
q为空,则所有层遍历结束
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 vector <vector <int >> level_order(TreeNode* root) { vector <vector <int >> res; if (root == nullptr ) { return res; } queue <TreeNode*> q; q.push(root); while (!q.empty()) { int level_num = q.size(); vector <int > curv; for (int i = 0 ; i < level_num; i++) { TreeNode* p = q.front(); if (p->left) q.push(p->left); if (p->right) q.push(p->right); q.pop(); curv.push_back(p->val); } res.push_back(curv); } return res; }
发现重复的数字-287
Find the Duplicate Number , 类似于aim2offer中查找重复的数字
数组a,有n+1个数,都在[1,n]范围内,只有一个重复的元素。找到它
二分思路
[1, n]
这个范围有n个数。划分为两个范围[1, m]
和[m+1, n]
每次去遍历整个数组 ,统计两个范围 内的数字的数目
统计整个数组中元素在[1, m]
范围内的个数\(c_1\)
统计整个数组中元素在[m+1, n]
范围内的个数\(c_2\)
[1, m]这m个数字的数量是c
如果 c > m
, 则1, m]
内一定存在重复的数,e = m
否则,[m+1, n]
一定存在重复的数,s = m + 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 45 46 47 48 49 50 51 int find_duplicate (const vector <int >& a) { int n = a.size() - 1 ; if (n <= 0 ) { return 0 ; } int l = 1 ; int r = n; int dup = -1 ; while (l <= r) { int m = (l + r) >> 1 ; int count = count_range(a, l, m); if (l == r) { if (count >= 2 ) { dup = l; break ; } } if (count > (m - l + 1 )) { r = m; } else { l = m + 1 ; } } return dup; } int count_range (const vector <int >& a, int min, int max) { int count = 0 ; for (int i = 0 ; i < a.size(); i++) { if (a[i] >= min && a[i] <= max) { count++; } } return count; }
合并两条有序链表-021
Merge Two Sorted Lists ,和归并排序的Merge操作 很类似。
考虑鲁棒性
思路
\(l_1\) 与\(l_2\) 若有一个为空的,则返回另一个
初始化新的head ,选择\(l_1\) 与\(l_2\) 中第一个节点较小的那个
while循环,谁小选谁
结束之后,直接把未空的链表链接上 即可
关键代码
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 ListNode* mergeTwoLists (ListNode* l1, ListNode* l2) { if (l1 == nullptr ) { return l2; } if (l2 == nullptr ) { return l1; } ListNode* head = nullptr ; if (l1->val < l2->val) { head = l1; l1 = l1->next; } else { head = l2; l2 = l2->next; } ListNode* p = head; while (l1 && l2) { if (l1->val < l2->val) { p->next = l1; l1 = l1->next; } else { p->next = l2; l2 = l2->next; } p = p->next; } if (l1) { p->next = l1; } if (l2) { p->next = l2; } return head; }
合并多条有序链表-023
Merge k Sorted Lists
我们已经会合并2条链表了,可以使用归并排序 和二分查找 的思想来合并多个列表。
示例
现在有1, 2, 3, 4, 5, 6
条链表
第一步:1-6
,2-5
,3-4
合并,得到新的1, 2, 3
第二步:1-3
合并,2不动,得到新的1, 2
第三步:1-2
合并, 得到新的2
, 合并完成
返回list[0]
总结
直到len==1
合并到只有一条链表 ,合并到list[0]
对于当前len,折半两两合并 ,i
和len-i-1
合并,放到前面lists[i]
len缩减一半 ,len=(len+1)/2
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ListNode* mergeKLists (vector <ListNode*>& lists) { if (lists.empty()) { return nullptr ; } int len = lists.size(); while (len > 1 ) { for (int i = 0 ; i < len / 2 ; i++) { lists[i] = mergeTwoLists(lists[i], lists[len - i - 1 ]); } len = (len + 1 ) / 2 ; } return lists[0 ]; }
Z型打印二叉树-103
Binary Tree Zigzag Level Order Traversal
参考层次遍历 一次遍历一层 的思路。
一次遍历一层,得到当前层的遍历结果
单数,从左向右;偶数,从右向左
每次遍历一个元素,把左右孩子入队
根节点,向右走
第二层,向左走
向右走,从队头出,孩子先左后右,加到队尾
向左走,从队尾出,孩子先右后左,加到队首
两个栈的思路
栈1初始存放根节点,栈2为空
向右 走,栈1全部出栈 ,先左后右 孩子依次压入栈2,栈底-栈顶,栈2为2 3
向左 走,栈2全部出栈 ,先右后左 孩子依次压入栈1,栈1为7 6 5 4
向右走,栈1出栈,左右孩子依次压入栈2,栈2为8 9 10 11 12 13 14 15
向左走,栈2出栈,结束
关键代码
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 vector <vector <int >> zigzagLevelOrder(TreeNode* root) { vector <vector <int >> res; if (root == nullptr ) { return res; } stack <TreeNode*> st1; stack <TreeNode*> st2; st1.push(root); while (!st1.empty() || !st2.empty()) { vector <int > curv; if (!st1.empty()) { while (!st1.empty()) { TreeNode* p = st1.top(); st1.pop(); curv.push_back(p->val); if (p->left) st2.push(p->left); if (p->right) st2.push(p->right); } } else { while (!st2.empty()) { TreeNode* p = st2.top(); st2.pop(); curv.push_back(p->val); if (p->right) st1.push(p->right); if (p->left) st1.push(p->left); } } res.push_back(curv); } return res; }
二叉树路径求和-112.113.437
题目1 从根节点到叶子求和-112
Path Sum , EASY
给一颗二叉树和一个sum值,判断是否有从根节点到叶子节点的路径,使得路径上的节点求和等于sum
思路
做减法
根节点为空,False
没有孩子,判断root.val == sum
有孩子,把sum减掉根节点的值,去判断左右子树是否有 ,sum = sum - root.val
关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 bool hasPathSum (TreeNode* root, int sum) { if (root == nullptr ) { return false ; } if (!root->left && !root->right) { return root->val == sum; } int newsum = sum - root->val; return hasPathSum(root->left, newsum) || hasPathSum(root->right, newsum); }
题目2 从根节点到叶子节点求和-保存路径-113
Path Sum-113 , Medium
给二叉树和sum值,找到root-to-leaf的路径,使得和为sum。保存该路径
思路
用vector<int> path
来记录当前路径 , vector<vector<int>> res
记录最终结果
根节点为空,返回
到达叶子节点,val == sum
, 把当前节点加入path ,当前path加入res , 否则返回
非叶子节点,把当前节点加入path ,去左右子树中遍历 ,继续追加path直到叶子节点
非叶子节点,结束后,把当前节点从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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 vector <vector <int >> pathSum(TreeNode* root, int sum) { vector <vector <int >> res; if (root == nullptr ) { return res; } vector <int > path; find_path(root, sum, path, res); return res; } void find_path (TreeNode* root, int sum, vector <int >& path, vector <vector <int >> &res) { if (root == nullptr ) { return ; } if (!root->left && !root->right) { if (root->val == sum) { path.push_back(root->val); res.push_back(path); path.pop_back(); } return ; } path.push_back(root->val); int newsum = sum - root->val; if (root->left) { find_path(root->left, newsum, path, res); } if (root->right) { find_path(root->right, newsum, path, res); } path.pop_back(); }
题目3 求二叉树的所有和为sum的路径,任意起始节点-437
给一颗二叉树和sum,求出所有和为sum的路径数量,从任意节点开始和结束。
思路
层次遍历,以每一颗节点为起始值 ,找到以它开始的路径数量
节点为空,0
无孩子,不相等,0
无孩子,相等,1
有孩子,相等,c = 1
, 不相等c = 0
。 更新sum ,继续递归查找左右孩子的count 。返回c+count
关键代码
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 int pathSum (TreeNode* root, int sum) { if (root == nullptr ) { return 0 ; } queue <TreeNode*> q; q.push(root); int count = 0 ; while (!q.empty()) { TreeNode* now = q.front(); q.pop(); count += count_from_root(now, sum); if (now->left) q.push(now->left); if (now->right) q.push(now->right); } return count; } int count_from_root (TreeNode* root, int sum) { if (root == nullptr ) return 0 ; int c = 0 ; if (sum == root->val) { c = 1 ; } else if (!root->left && !root->right) { return 0 ; } int newsum = sum - root->val; return c + count_from_root(root->left, newsum) + count_from_root(root->right, newsum); }
有序链表转平衡BST-109
Convert Sorted List to Binary Search Tree , Medium
。 类似题型:BST转有序双向链表
给一个有序链表,转化为平衡的二叉搜索树
思路
有序 -- BST的中序遍历;平衡 -- 以中间节点为根节点,分为左右子树递归去创建。
计算总结点数量 -- size
, 递归去构建树go(0, size - 1)
go(head, start, end)
,计算出中间节点 mid-node
, 构造树根 root
左孩子 root.left = go(head, start, mid-1)
, 右孩子 root.right = go(now.next, mid+1, end)
返回当前树根节点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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 TreeNode* sortedListToBST (ListNode* head) { if (head == nullptr ) { return nullptr ; } int size = 0 ; ListNode* p = head; while (p) { ++size; p = p->next; } return create_tree(head, 1 , size); } TreeNode* create_tree (ListNode* head, int start, int end) { if (head == nullptr || start > end) { return nullptr ; } int mid = (end + start) / 2 ; ListNode* node = head; for (int i = start + 1 ; i <= mid; i++) { node = node->next; } TreeNode* root = new TreeNode(node->val); root->left = create_tree(head, start, mid - 1 ); root->right = create_tree(node->next, mid + 1 , end); return root; }
序列化二叉树-297
Serialize and Deserialize Binary Tree , Hard
把二叉树序列化为字符串,把字符串反序列化为一棵树
思路
前序遍历 来保存序列,保存成一颗完全二叉树 ,空节点用$
表示,使用空格进行分割。
序列化
序列化为一颗完全二叉树,先序递归。遇到空指针,则用$
代替
先把字符放到stringstream
里面,<<
输入, 最后s.str()
得到字符串
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 string serialize (TreeNode* root) { if (root == nullptr ) { return "" ; } stringstream buf; build_string(root, buf); return buf.str(); } void build_string (TreeNode* root, stringstream & buf) { if (root == nullptr ) { buf << "$" << " " ; return ; } buf << root->val << " " ; build_string(root->left, buf); build_string(root->right, buf); return ; }
从字符串中解析得到序列,存到队列中
1 2 3 4 5 6 7 8 9 10 11 12 void split (const string & str, queue <string > &q, const char delim = ' ' ) { istringstream input; input.str(str); string line; while (std ::getline(input, line, delim)) { q.push(line); } return ; }
反序列化
得到队列 序列之后,可以对其进行递归反序列化构建树。先序序列,不是层次序列。
根-左-右 ,队列。出队 ,建立根节点
左-右 ,队列,递归建立左孩子
右 ,队列,建立右孩子
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 TreeNode* deserialize (const string & data) { if (data.empty()) { return nullptr ; } queue <string > preorder; split(data, preorder); return build_tree(preorder); } TreeNode* build_tree (queue <string >& pres) { if (pres.size() == 0 ) { return nullptr ; } string val = pres.front(); pres.pop(); if (val == "$" ) { return nullptr ; } TreeNode* root = new TreeNode(std ::stoi(val)); root->left = build_tree(pres); root->right = build_tree(pres); return root; }
数字全排列-046
Permutations , Medium
。 搜索树
给一个数组,返回全排列。每个数字都不相同
思路
全排列回溯法
搜索。一个数组,搜索第t层的时候
前面t-1层都已经ok
遍历后面的所有元素,给到t层,去搜索
每次进行交换
github代码
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 vector <vector <int >> permute(vector <int > &nums) { vector <vector <int >> res; dfs(nums, 0 , res); return res; } void dfs (vector <int >& path, int t, vector <vector <int >>& res) { if (t >= path.size()) { res.push_back(path); return ; } for (int i = t; i < path.size(); i++) { std ::swap(path[t], path[i]); dfs(path, t + 1 , res); std ::swap(path[t], path[i]); } }
重复数字全排列-047
重复数字全排列-047
给一个数组,里面有一些重复的数字,给出所有的排列可能
思路
重复的原因:当为t设置值的时候,遍历后面的所有元素给t赋值,但是后面都有一些重复的数值。
比如说 1 2 1 1
, 开始是1,1会与最后的两个1再进行交换,然而其实是一样的。没必要了。
每次遍历交换的时候,只交换遍历后面不重复的元素 。
github代码
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 void dfs (vector <int >& path, int t, vector <vector <int >>& res) { if (t >= path.size()) { res.push_back(path); return ; } set <int > vals; set <int > idx; for (int i = t; i < path.size(); i++) { if (vals.find(path[i]) == vals.end()) { vals.insert(path[i]); idx.insert(i); } } for_each(idx.begin(), idx.end(), [&](int i) { std ::swap(path[t], path[i]); dfs(path, t + 1 , res); std ::swap(path[t], path[i]); }); }
组合问题
给n个字符,要组成m个字符,问有多少种组成方法
有点类似于01背包的选择。
选择第一个字符,则在后面选择m-1个字符
不选择第一个字符,则在后面选择m个字符
正方体顶点和相等问题
给8个数字,正方体有8个顶点,数字放在顶点上。使得3对对面的顶点和相等。
也就是搜索,然后限定一些条件。
8皇后问题
8*8的象棋摆8个皇后,任意两个皇后不能在同一行、同一列或同一对角线上。问有多少种摆法
N皇后问题-051
n*n的棋盘摆n个皇后,任意两个皇后不能在同一行、同一列或同一对角线上。返回摆法。
N-Queens
每一行一个皇后,每一行有n个选择。就去dfs搜索 所有的排列树。
path[t]=k
, 第t行的皇后在第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 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 vector <vector <string >> solveNQueens(int n) { vector <vector <int >> locations; vector <int > path(n); std ::iota(path.begin(), path.end(), 0 ); dfs(0 , n, path, locations); vector <vector <string >> res; for (auto loc : locations) { vector <string > solu; for (int i : loc) { string line (n, '.' ) ; line[i] = 'Q' ; solu.push_back(line); } res.push_back(solu); } show(res[0 ]); return res; } void dfs (int t, int n, vector <int >& path, vector <vector <int >>& res) { if (t == n) { res.push_back(path); return ; } for (int i = t; i < n; i++) { if (legal(t, path[i], path) == true ) { swap(path[i], path[t]); dfs(t + 1 , n, path, res); swap(path[i], path[t]); } } } bool legal (int t, int k, const vector <int >& path) { for (int i = 0 ; i <= t - 1 ; i++) { if (path[i] == k) { return false ; } if (t - i == k - path[i]) { return false ; } if (t + k == i + path[i]) { return false ; } printf ("p[%d]=%d,p[%d]=%d\n" , t, k, i, path[i]); } return true ; }
N皇后问题-052
返回有多少种解法
做了上面的题,那这个就很简单了,返回数量就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int solveNQueens (int n) { vector <vector <int >> locations; vector <int > path(n); std ::iota(path.begin(), path.end(), 0 ); dfs(0 , n, path, locations); return locations.size(); }
查找第k大的数总结
给N个数,确定第k个最大值
1 排序
排好序,取出第k大的值。\(O(n\log n + k)\)
2 简单选择排序
简单选择 。第k次选择,就是第k大的数字。\(O(n*k)\)
3 快速排序思想
每次partition,会把x放到位置i上。注意partition要从大到小排列,左大右小 ,而不是普通排序的左小右大。
i == k
, 则就是a[i]
k > i
, 则在i的右边
k < 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 int find_kth_num (vector <int > &a, int l, int r, int k) { int i = partition(a, l, r); if (i + 1 == k) { return a[i]; } else if (i + 1 > k) { return find_kth_num(a, l, i - 1 , k); } else { return find_kth_num(a, i + 1 , r, k); } }
4 最大堆
\(O(4*n)\) 的空间建立最大堆,pop k次即可。\(O(4 \times n + k\times\log n)\)
5 最小堆
维护大小为k的最小堆,遍历数组
堆顶元素大,则不管
堆顶元素小,则把当前值插入堆中
最后的堆顶,就是第k大的元素
\(O(n \times \log k)\)
6 Hash法
查找最小的k个数的总结
给一个数组,找到最小的k个数。注意改变或不改变原数组
1 排序思路
对n个数字从小到大排好序,再取前k个数。O(nlogn + k)=O(nlogn)
。排序算法总结
2 快速排序
排好前k个即可,改变原数组。
3 最大堆
建立大小为k的最大堆。不改变原数组。遇到新的元素,小于堆顶,则加入
4 堆排序
对整个数组n进行堆排序,每次取堆顶,取k次。
数组中次数超过一半的数-169
Majority Element-169 , easy
一个数组,有一个元素出现次数超过一半,找到它。
思路0 先排序再找
\(O(n\log n)\)
思路1 快速排序查找第k大元素思想
快速排序笔记 。
如果排好序,则该重复的数字应该在数组中间\(a_{\frac{n}{2}}\) 。 也就是中位数,第n/2
大的数字
问题就转化为查找数组中的K大的元素
查找第k大的元素
partition(a, l, r)
会把x=a[l]
放到中间去,小于的在右边,大于的在左边。返回x的最终位置i
i == k
, 则x就是第k大的元素
k < i
, 则k在右边
k > i
, 则k在左边
继续查找,知道 i == k
思路2 count加加减减思想
遇见友军(相同的),就++
遇见敌军(不同的),就--
最后剩余的肯定就是人数最多的那个(数字)
[关键代码]
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 int majority_element (vector <int >& a) { if (a.size() == 0 ) { return 0 ; } int res = a[0 ]; int count = 1 ; for (int i = 1 ; i < a.size(); i++) { if (a[i] == res) { count++; } else { if (count == 0 ) { res = a[i]; count = 1 ; } else { count--; } } } return res; }
主元素2-229
给一个数组,找到所有出现次数超过n/3的数
Majority Element II , medium
。
思路
当然最终结果只有2个或1个。思路同阵地攻守。
用两个变量去记录两个主元素
有一个相同,对应加1
两个都不同,有一个count==0
, 则重置
两个都不听,两个都有count,则都减减
遍历之后,得到两个数,两个count
返回count > n/3
的数
最后,一定要注意去重 !n1 != n2
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 vector <int > majority_element(const vector <int >& nums) { vector <int > res; if (nums.empty()) { return res; } int n1 = 0 , c1 = 0 ; int n2 = 0 , c2 = 0 ; for (int n : nums) { if (n == n1) { c1++; } else if (n == n2) { c2++; } else if (c1 == 0 ) { n1 = n; c1 = 1 ; } else if (c2 == 0 ) { n2 = n; c2 = 1 ; } else { c1--; c2--; } } c1 = 0 , c2 = 0 ; for (auto n : nums) { if (n1 == n) { c1++; } else if (n2 == n) { c2++; } } if (c1 > nums.size() / 3 ) { res.push_back(n1); } if (n2 != n1 && c2 > nums.size() / 3 ) { res.push_back(n2); } return res; }
最长不重复子字符串-003
Longest Substring Without Repeating Characters-003
给一个字符串,找到里面最长子字符串的长度。
1 DP思路
设l[i]=k
, 是以s[i]
结尾 最长字符串的长度是k。从左到右进行计算 。遍历到s[i]
1、 s[i]
在前面没有出现过 ,l[i] = l[i-1] + 1
2 、s[i]
在前面出现过 ,计算该字符现在与前面两次出现的距离d ,比较d和s[i-1]
的最大长度l[i-1]
d > l[i-1]
, d很远 (不在i-1的最长字符串里),l[i] = l[i-1] + 1
d <= l[i-1]
,d很近 (在i-1的最长字符串里),l[i]=d
使用HashMap<Char, 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 int long_substr_len (const string & s) { if (s.empty()) { return 0 ; } vector <int > l(s.length(), 0 ); map <char , int > m; for (int i = 0 ; i < s.length(); i++) { char ch = s[i]; if (i == 0 ) { l[i] = 1 ; m[ch] == i; continue ; } if (m.find(ch) == m.end()) { l[i] = l[i-1 ] + 1 ; } else { int d = i - m[ch]; if (d > l[i-1 ]) { l[i] = l[i-1 ] + 1 ; } else { l[i] = d; } } m[ch] = i; } return *max_element(l.begin(), l.end()); }
丑数-263-264-313
263-判断是否是丑数 ,easy
只包含2、3、5作为因子的正整数是丑数,1也是。判断是否是丑数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool isUgly (int num) { if (num <= 0 ) { return false ; } if (num == 1 ) { return true ; } vector <int > a = {2 , 3 , 5 }; for (auto i : a) { while (num % i == 0 ) { num /= i; } } return num == 1 ; }
264-找到第n个丑数 ,medium
找到第n个丑数
如果依次找,所有的数都要分解求余,效率低。**丑数=丑数*{2,3,5}。 用数组去存放丑数, 从1开始依次向前面递推计算。**
用3个索引 ,去分别保存乘以 2,3,5
的基数,每次选择最小的 作为下一个丑数,同时更新对应的索引++ 。
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 int nthUglyNumber (int n) { if (n <= 0 ) { return -1 ; } if (n == 1 ) { return 1 ; } vector <int > u(n); u[0 ] = 1 ; int t2 = 0 , t3 = 0 , t5 = 0 ; for (int i = 1 ; i < n; i++) { int cur = min(min(u[t2] * 2 , u[t3] * 3 ), u[t5]*5 ); u[i] = cur; if (cur == u[t2] * 2 ) { t2++; } if (cur == u[t3] * 3 ) { t3++; } if (cur == u[t5] * 5 ) { t5++; } } return u[n-1 ]; }
313-超级丑数 , medium
超级丑数是,给一个素数列表去计算丑数 ,不再局限于2,3,5去计算丑数。
给一个素数列表和n,返回第n个丑数
和上一个思路一致,用数组保存。注意t数组的初始大小是primes.size(),而不是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 int nthSuperUglyNumber (int n, vector <int >& primes) { if (n == 1 ) { return 1 ; } if (n <= 0 || primes.empty()) { return -1 ; } vector <int > u(n); u[0 ] = 1 ; vector <int > t(primes.size(), 0 ); for (int i = 1 ; i < n; ++i) { int cur = INT_MAX; for (int j = 0 ; j < primes.size(); ++j) { cur = min(cur, u[t[j]] * primes[j]); } u[i] = cur; for (int j = 0 ; j < primes.size(); ++j) { if (cur == u[t[j]] * primes[j]) { t[j]++; } } } return u[n-1 ]; }