二分及变形
文章目录
- 二分及变形
- 基础二分
- 二分变形
- leetcode [410. 分割数组的最大值](https://leetcode-cn.com/problems/split-array-largest-sum/)
- leetcode [875. 爱吃香蕉的珂珂](https://leetcode-cn.com/problems/koko-eating-bananas/)
- leetcode [1011. 在 D 天内送达包裹的能力](https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/)
- 牛牛找子集
- [4. 寻找两个正序数组的中位数](https://leetcode-cn.com/problems/median-of-two-sorted-arrays/)
- [leetcode 378. 有序矩阵中第K小的元素](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/)
- DP/滚动数组
- 区间DP
- 路径上取值
- [newcoder 矩阵的最小路径和 ](https://www.nowcoder.com/practice/2fb62a4500af4f4ba5686c891eaad4a9?tpId=101&&tqId=33254&rp=1&ru=/ta/programmer-code-interview-guide&qru=/ta/programmer-code-interview-guide/question-ranking)
- [leetcode 329. 矩阵中的最长递增路径](https://leetcode-cn.com/problems/longest-increasing-path-in-a-matrix/)
- newcoder寻宝
- 树形DP
- 背包问题
- 滑动窗口 单调栈/队列
- DFS
- [leetcode 104. 二叉树的最大深度](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/)
- [111. 二叉树的最小深度](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/)
- [leetcode 78. 子集 I /II](https://leetcode-cn.com/problems/subsets/)
- [46. 全排列I / II 通用](https://leetcode-cn.com/problems/permutations/)
- [784. 字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/)
- [39. 组合总和](https://leetcode-cn.com/problems/combination-sum/)
- BFS
- * 图
- * 树
- TOP K 问题
- 众数/中位数
- 背包问题
- 回文问题
- 技巧/智力题
- 模拟题
- 数学题
- 数组
- 链表
- 贪心
- STL
- 模板存储
基础二分
整数二分 算法模板
// 整数二分算法模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
浮点数二分 算法模板
// 浮点数二分算法模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
vector<int> xx;
lower_bound(xx.begin(), xx.end(), num);
//返回迭代器
//迭代器指向的数 <= num的最左侧
upper_bound();
//迭代器指向的数 > num 的最小值
二分变形
leetcode 410. 分割数组的最大值
设计算法让子数组的最大值最小。
分析题意应该是找一个容量,容量越小越好,并且还能保证分成m组。
class Solution {
public:
/*
left最小分组容量 right 最大容量
二分找 合适容量
mid = left + right >> 1;
首先将数组中的值塞入temp中,如果大于mid就再申请一个分组。
随后根据count分组大小调整容量边界。
count > m 分组太多,分组的容量小了, left = mid + 1;
count <=m 分组正好,或者分组少了,容量太大。r = mid;
这样可以收缩边界,
*/
int splitArray(vector<int>& nums, int m) {
long left = 0;
long right = 0;
for(long x:nums) {
left = max(left, x);
right += x;
}
while(left < right){
long mid = left + right >> 1;
int count = 1;
long temp = 0;
for(int num : nums){
if(temp + num > mid){
count++;
temp = 0;
}
temp += num;
}
if(count > m) left = mid+1;
else right = mid;
}
return left;
}
};
leetcode 875. 爱吃香蕉的珂珂
跟上题差不多思路,数据比较坑
class Solution {
public:
/*
1.首先考虑数,和最少吃1根
2.分析下题意 珂珂最少吃一根或者(right / H)根,最多吃总数right根
3.题意中 吃完<=k根后就不吃了,简化了复杂度。那么一个堆够珂珂吃多长时间呢?一小时或者...详见if else
4.随后根据hours用时多少,来收缩范围[l, mid],[mid+1, r],这么收缩来收缩去,一定能找到满足条件的吃蕉速率。
为啥最后会求的满足的最小速率呢。因为正确答案的时候,右值r会收缩。最终答案在[l, mid]-> [mid, mid]中,所以为满足条件的最小的那个
*/
int minEatingSpeed(vector<int>& piles, int H) {
long long right = 0;
for(const auto& x:piles) right += x;
long long left = right / H;
left = max(left,1ll);
while(left < right){
long long mid = left + right >> 1;
int hours = 0;
for(const auto& x:piles){
if(x <= mid) hours++;
else if(x % mid == 0){
hours += (x / mid);
}else hours += (x / mid) + 1;
}
if(hours > H) left = mid +1;
else right = mid;
}
return left;
}
};
leetcode 1011. 在 D 天内送达包裹的能力
class Solution {
public:
/*
要根据题意,对最小容量left进行定义。
其他没啥
*/
int shipWithinDays(vector<int>& weights, int D) {
long right = 0;
long left = 0;
for(long x:weights){
left = max(left, x);
right += x;
}
while(left < right){
long mid = left + right >> 1;
int days = 1;
int temp = 0;
for(auto& x:weights){
if(temp + x > mid){
temp = 0;
days++;
}
temp += x;
}
if(days > D) left = mid+ 1;
else right = mid;
}
return left;
}
};
牛牛找子集
#include <unordered_map>
//二分分组的组数
class Solution {
public:
vector<int> solve(int n, int k, vector<int>& s) {
unordered_map<int, int> freq;
for (int num: s) {
++freq[num];//各个数字出现频次
}
int l = 1, r = n;// best = -1;
while (l < r) {
int mid = (l + r + 1) / 2;//分几组//提议要求分尽可能多的组
int cur = 0;//每组大小
for (auto it = freq.begin(); it != freq.end(); ++it) {
cur += it->second / mid;
}
if (cur >= k) {
//best = mid;
l = mid;
}
else {
r = mid - 1;//mid 计算要+1
}
}
vector<int> ans;
for (auto it = freq.begin(); it != freq.end(); ++it) {
int cnt = it->second / l;
for (int i = 0; i < cnt; ++i) {
ans.push_back(it->first);
}
}
sort(ans.begin(), ans.end());
ans.resize(k);//每组大小可能大于k
return ans;
}
};
4. 寻找两个正序数组的中位数
class Solution {
public:
int m, n;
//每次找 k/ 2 如果n1[] < n2[] 那么
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
m = nums1.size(), n = nums2.size();
int left = (m + n + 1) / 2, right = (m + n + 2) / 2;
//m + n 偶数 两个数 (l + r) / 2
// 奇数 还是中间那个数 l == r
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
int findKth(vector<int>& nums1,int i, vector<int>& nums2, int j, int k){
if(i >= m) return nums2[j + k - 1];
if(j >= n) return nums1[i + k - 1];
if(k == 1) return min(nums1[i], nums2[j]);
int midVal1 = (i + k / 2 - 1 < m) ? nums1[i + k / 2 - 1] : INT_MAX;
int midVal2 = (j + k / 2 - 1 < n) ? nums2[j + k / 2 - 1] : INT_MAX;
//较大的一方固定,抛弃较小的一方
if(midVal1 < midVal2){
return findKth(nums1, i + k / 2, nums2, j , k - k / 2);
}else{
return findKth(nums1, i, nums2, j + k / 2 , k - k / 2);
}
}
};
leetcode 378. 有序矩阵中第K小的元素
class Solution {
public:
bool check(vector<vector<int>>& matrix, int k, int mid){
int i = matrix.size() -1;
int j = 0;
int num = 0;
while(i >= 0 && j < matrix.size()){
if(matrix[i][j] <= mid){
num += i+ 1;//证明有i + 1个数 <= mid
j++;
}else i--;
}
return num >= k;//true 代表 第k个数 在left~mid之间
//num 为 mid是第几小
}
int kthSmallest(vector<vector<int>>& matrix, int k) {
int n = matrix.size();
int left = matrix[0][0];
int right = matrix[n-1][n-1];
while(left < right){
int mid = left + right >> 1;
if(check(matrix, k , mid)) right = mid;
else left = mid+ 1;
}
return left;
}
};
DP/滚动数组
leetcode 1014. 最佳观光组合
class Solution {
public:
int maxScoreSightseeingPair(vector<int>& A) {
int sz = A.size();
int left = 0, maxscore = 0;
for(int i = 0 ; i < sz; i++){
// addi = max(addi, x + i);
maxscore = max(maxscore, A[i] - i + left);
left = max(left, A[i] + i);
}
return maxscore;
}
};
/*
用二分?excuse me?
A[i] + i + A[j] - j 的最大值
且 i < j;
滚动数组即可
数据量50000,暴力算法指定超时
*/
###序列型DP
5471. 和为目标值的最大数目不重叠非空子数组数目
class Solution {
map<int, int > m;
int f[100005], s[100005];
//map 记录前缀和
//nums[L.....R] 之和等于 target
//nums[1.....R] sum[R]
//nums[1..L - 1] sum[R] - target
//f[R] = f[L -1] + 1;
//如果 sum - target 在开头处 那么 f[r] = 1;
public:
int maxNonOverlapping(vector<int>& nums, int target) {
int n = nums.size();
for(int i = 0; i < n; i++) s[i+1] = s[i] + nums[i];
//1....n
m.clear();
f[0] = 0;
m[0] = 0;
//m[sum - target] == 0 就是从开头转移过来的 所以 要是 0 + 1
//因为sum是前缀和 从开头开始计算
for(int i = 1; i <= n; i++){ //从1 到 n
f[i] = f[i-1];
if(m.count(s[i] - target))
f[i] = max(f[i], f[m[s[i] - target]] + 1);
m[s[i]] = i;//会覆盖前面的,正好 我只要靠后且满足的
}
return f[n];
}
};
###字符串DP
leetcode 392. 判断子序列
/*
思路跟最长上升子序列一样, dp[i][j]表示s[0:i]跟t[0:j]的公共字符个数
最后看dp[lens][lent] == lens就行(短的那个!!!!)
状态转移就是 这两句
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(s[i-1] == t[j-1]) dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
首先继承状态,随后判断状态
*/class Solution {
public:
bool isSubsequence(string t, string s) {
int lens = s.size(), lent = t.size();
int dp[lens+1][lent+1];
memset(dp, 0, sizeof dp);
for(int i = 1; i <=lens;i++)
for(int j = 1; j <= lent;j++)
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
if(s[i-1] == t[j-1]) dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
}
// cout << dp[lens][lent];
return dp[lens][lent] == lent;
}
};
区间DP
5486. 切棍子的最小成本
class Solution {
public:
int f[105][105];
int minCost(int n, vector<int>& cuts) {
cuts.push_back(0);cuts.push_back(n);
sort(cuts.begin(), cuts.end());
memset(f, 0x7f, sizeof f);
int m = cuts.size();
for(int i = 0; i < m - 1;i++)
f[i][i+1] = 0;
for(int len = 2; len < m ; ++len)
for(int i = 0; i + len < m; ++i)
for(int j = i + len, k = i + 1; k < j; k++)
//其实是枚举 切开cut[k]处, 所以cost才是 cuts[j] - cuts[i] 的长度
f[i][j] = min(f[i][j], f[i][k] + f[k][j] + cuts[j] - cuts[i]);
return f[0][m-1];
}
};
路径上取值
newcoder 矩阵的最小路径和
#include<iostream>
#include<cstring>
using namespace std;
const int N = 2010;
int f[N][N];
int dp[N][N];
int n,m;
int main(){
cin >> n>>m;
for(int i = 1;i<=n;i++)
for(int j = 1; j <= m;j++)
cin >> f[i][j];
memset(dp, 0x3f,sizeof dp);
dp[1][1] = f[1][1];
for(int i = 1; i <= n;i++)
for(int j = 1; j <= m;j++)
{
if(i==1&&j==1)continue;
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + f[i][j];
}
cout << dp[n][m];
return 0;
}
leetcode 329. 矩阵中的最长递增路径
class Solution {
public:
/*
优先队列+类似bfs+dp
首先对matrix值从小到大排序
首先处理值小的,搜索四个方向 要是有值比它小的就max(self,dp + 1)
最后搜索全部的dp 取最大值
*/
struct A{
int x, i, j;
bool operator> (const A& a)const{
if(x != a.x) return x > a.x;
if(i != a.i) return i > a.i;
return j > a.j;
}
};
priority_queue<A, vector<A>, greater<A>> minheap;
int longestIncreasingPath(vector<vector<int>>& matrix) {
if(!matrix.size()) return 0;
int m = matrix.size(), n = matrix[0].size();
if(!m || !n) return 0;
for(int i= 0; i< m;i++)
for(int j = 0; j < n;j++)
minheap.push({matrix[i][j], i, j});
vector<vector<int>> dp(m+1, vector<int>(n+1, 1));
int dx[] = {-1,0,1,0}, dy[] ={0,1,0,-1};
while(!minheap.empty()){
auto t = minheap.top();
minheap.pop();
// dp[t.i][t.j] = 1;
for(int i = 0; i< 4;i++){
int a = t.i + dx[i], b = t.j + dy[i];
if(a >=0 && a < m && b >= 0 && b < n && t.x > matrix[a][b])
dp[t.i][t.j] = max(dp[t.i][t.j], 1 + dp[a][b]);
}
}
int res = 0;
for(int i= 0; i< m;i++)
for(int j = 0; j < n;j++)
res = max(res, dp[i][j]);
return res;
}
};
newcoder寻宝
class Solution {
public:
const int MOD = 1e9 + 7;
long long dp[1010][1010];
int GetNumberOfPath(int n, int m, int x0, int y0, int x1, int y1) {
// write code here
memset(dp, 0, sizeof(dp));
dp[1][1] = 1;
for(int i = 1; i <= n;i++)
for(int j = 1; j <= m;j++){
if(i == 1 && j == 1) continue;
if(i >= x0 && j >= y0 && i <= x1 && j <= y1) continue;
dp[i][j] = dp[i-1][j] + dp[i][j-1], dp[i][j] %= MOD;
}
return dp[n][m];
}
/*
这个其实 那个求多少条路径到终点的很相似
用yxc的那个方法想就行
最后一个位置 只能从相邻的两个位置走过来, 情况就是两者相加
然后递归的想
*/
};
树形DP
337. 打家劫舍 III
DP:根据之前的状态来更新当前状态
class Solution {
public:
int rob(TreeNode* root) {
if(!root) return 0;
vector<int> res = dfs(root);
return max(res[0], res[1]);
}
vector<int> dfs(TreeNode* root){
if(!root) return {0,0};
//要用到下层节点,所以下处理左右子树
auto left = dfs(root->left);
auto right = dfs(root->right);
int dp0, dp1;
//dp0 不选该节点 max(left[0], left[1]) 意思是 可以选下层的节点,也可以不选
//dp1 选择该节点,那么就不能选左右子树的根节点,所以只能+left0 + right[0] 因为这两个的定义是 不选根节点
dp0 = max(left[0], left[1]) + max(right[0], right[1]);
dp1 = root->val + left[0] + right[0];
return {dp0, dp1};
}
};
背包问题
class Solution {
public:
/**
* @param nums: the given array
* @return: the minimum difference between their sums
*/
// 类似于背包问题,选取物件使得他们的sum尽量接近sum(nums)/2
//如果正好选到 sum/ 2 那么差值为0;
int findMin(vector<int> &nums) {
// write your code here
int sum = 0;
for(auto x:nums) sum += x;
vector<bool> dp(sum /2 + 1);
dp[0] = true;//能找到和为0 的集合
for(auto& num:nums){
for(int i = sum / 2; i >= num; --i)
dp[i] = (dp[i] || dp[i- num]);
}
int i = dp.size() - 1;
for(; i >=0; i--)
if(dp[i]) break;
return sum - 2 * i;
}
};
滑动窗口 单调栈/队列
leetcode 632. 最小区间
class Solution {
public:
/*
将所有数组合并,并且每个数附带上原数组的组别
这样 right向右滑,如果cnt == k 即已经覆盖所有数组,left就向右滑,来更新ans
*/
vector<int> smallestRange(vector<vector<int>>& nums) {
vector<pair<int,int>> newVec;
int k = nums.size();
for(int i = 0; i < nums.size();i++){
for(int& x:nums[i]){
newVec.push_back({x,i});//数字+组别
}
}
sort(newVec.begin(), newVec.end());
unordered_map<int,int> hash;
vector<int> ans;
int left = 0, n = newVec.size(), differ = INT_MAX, cnt = 0;
//左边 区间长度 区间内数组个数
for(int right = 0; right < n;right++){
if(hash[newVec[right].second] == 0) cnt++;
hash[newVec[right].second]++;
while(cnt == k && left <= right){
if(differ > newVec[right].first - newVec[left].first){
differ = newVec[right].first - newVec[left].first;
ans = {newVec[left].first, newVec[right].first};
}
if(--hash[newVec[left].second] == 0) --cnt;
left++;
}
}
return ans;
}
};
重叠区间最大个数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4g4a3odm-1616578457385)(/Users/yangfan/Code/计算机相关基础/算法题整理.assets/截屏2020-08-01 19.49.48.png)]
遇到区间开头+1, 区间结尾-1;
思路真是太巧妙了
妞妞的木板
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ylGLSPXU-1616578457389)(/Users/yangfan/Code/计算机相关基础/算法题整理.assets/image-20200806221808900.png)]
class Solution {
public:
/**
*
* @param n int整型
* @param m int整型
* @param a int整型vector
* @return int整型
双指针 当i向右移, 如果区间内黑色块数量大于m,l就向左移,直到满足num <= m;
*/
int solve(int n, int m, vector<int>& a) {
// write code here
int ans = 0, num = 0, l = 0;
for(int i = 0; i < a.size(); i++){
num += (a[i] == 0);
while(num > m){
num -=(a[l] == 0);
l++;
}
ans = max(ans, i - l + 1);
}
return ans;
}
};
DFS
leetcode 104. 二叉树的最大深度
class Solution {
public:
/*如果root == null
就说明到底了,return 0;
否则返回左右子树最高值 + 1;
*/
int maxDepth(TreeNode* root) {
if(!root) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
111. 二叉树的最小深度
class Solution {
public:
/*求最小深度,根节点到叶节点的距离。
要断定他是叶节点!*/
int minDepth(TreeNode* root) {
if(!root) return 0;
int left = minDepth(root->left);
int right = minDepth(root->right);
if(!left || !right) return left + right + 1;//断定他是叶节点
//left = 0 and rigth = 0 返回1无误
//left = 0 or right = 0 返回想加之和
return min(left, right) +1;
}
};
leetcode 78. 子集 I /II
递归➕回溯. // 遍历添加新元素
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
dfs(nums, 0);
return ans;
}
void dfs(vector<int>& nums, int start){
ans.push_back(path);
for(int i = start; i < nums.size();i++){
path.push_back(nums[i]);
dfs(nums, i+1);
path.pop_back();
}
}
};
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
vector<vector<int>> subsets(vector<int>& nums) {
//每次将新元素加进去当作新的集合
ans.push_back(path);
for(auto it:nums){
int n = ans.size();
for(int i = 0; i < n;i++){
path = ans[i];
path.push_back(it);
ans.push_back(path);
}
}
return ans;
}
};
//可以分析 遍历重复的情况。发现如果有重复数字 之与上一轮生成的序列合并并添加进ans即可
//len 记录上一轮新生成序列的长度 或者 全序列的长度
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> ans;
vector<int> v;
ans.push_back(v);
int right = 1, left = 0, len = 0;
for(int i = 0; i < nums.size(); i++){
if(i != 0 && nums[i] == nums[i-1]) left = ans.size() - len;
else left = 0;
right = ans.size();
len = right - left;
for(int j = left; j < right; ++j){//循环容易学错
v = ans[j];
v.push_back(nums[i]);
ans.push_back(v);
}
}
return ans;
}
};
46. 全排列I / II 通用
//无重复数字
class Solution {
public:
vector<vector<int>> ans;
vector<int> path;
int n;
vector<vector<int>> permute(vector<int>& nums) {
n = nums.size();
path.resize(n);
//sort(nums.begin(), nums.end());
dfs(nums, 0, 0, 0);
return ans;
}
void dfs(vector<int>& nums, int u, int start, int st){
//数组的第u位
//path中放置位置 存储位置
if(u == n){
ans.push_back(path);
return;
}
//如果有重复数字
//if(u != 0 || nums[u] != nums[u-1]) start = 0;
for(int i = start; i < n; i++){
if(!((st >> i) & 1)){
path[i] = nums[u];
dfs(nums, u+1, i + 1, st +(1<<i));
//. 数组下一个值
//当前位置的下一个
}
}
}
};
784. 字母大小写全排列
class Solution {
public:
vector<string> ans;
vector<string> letterCasePermutation(string S) {
if(S.empty()) return ans;
dfs(S,0);
return ans;
}
void dfs(string S, int u){
//DFS先写基线条件,没个return能行?
if( u == S.size()) {
ans.push_back(S);
return;
}
dfs(S,u+1); //搜索树,先搜 该位不变的情况;
//下面搜变得
if(S[u] >= 'A'){
S[u] ^= 32;//大小写互相转换
//'A' 65 化成二进制更好理解
//'a' 97 只在32数处不同 该运算可理解为取反
//32
dfs(S,u+1);
}
}
};
39. 组合总和
class Solution {
public:
vector<vector<int>> res;
vector<int> tmp;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
dfs(candidates, target, 0, 0); //sum,begin
return res;
}
void dfs(const vector<int>& candidates,const int target, int sum, int begin){
if(sum == target){
res.push_back(tmp);
return;
}else{
for(int i = begin; i < candidates.size(); ++i){
if(sum + candidates[i] <= target){
tmp.push_back(candidates[i]);
dfs(candidates, target, sum + candidates[i], i);
//数字可以无限选择 所以还可以在i处选
tmp.pop_back();
}
}
}
}
};
BFS
* 图
####DFS 牛牛打怪兽 临接表
/**
* struct Point {
* int x;
* int y;
* };
*/
const int N =1e5+ 100;
int cnt = 0;
vector<int> e[N];//N个vector<int> 容器
vector<int> f;
class Solution {
public:
void dfs(int x,int fa,int num){
int son = 0;
for(int i = 0; i < e[x].size(); i++){
int y = e[x][i];//与其相连的点
if(y == fa) continue;//在临接表中找到父亲 则跳过
son++;
dfs(y, x, num + f[y-1]);
}
if(son == 0 && num <= 2) cnt++;
}
int solve(int n, vector<Point>& Edge, vector<int>& _f) {
// write code here
f = _f;
for(int i = 0; i < n-1; i++)
{
e[Edge[i].x].push_back(Edge[i].y);
e[Edge[i].y].push_back(Edge[i].x);
}
cnt = 0;
dfs(1,0,f[0]);
return cnt;
}
};
* 树
Leetcode 5474. 好叶子节点对的数量 # 树上统计
找满足叶子结点之间满足条件的对儿数量
const int MAXN = 1100;
int cnt[MAXN][15], tot, ans;
//cnt[x][d] 以 x 为根的子树内,距离 x 为 d 的**叶子结点**数量
class Solution {
public:
int dfs(TreeNode* root, const int D){
int x = ++ tot;//节点编号
int l = 0, r = 0;
if(root->left) l = dfs(root->left, D);//得到左右子树根节点编号
if(root->right) r = dfs(root->right, D);
if(l > 0 && r > 0){//左右子树 同时存在
for(int i = 0; i <= D;i++)//i代表左子树节点到左子树根节点L的距离
for(int j = 0; j <=D; j++)//j代表左子树节点到左子树根节点R的距离
if(2+i+j <=D) ans += cnt[l][i] * cnt[r][j];
}
for(int i = 0; i< D;i++){
cnt[x][i+1] = cnt[l][i] + cnt[r][i];//l和r是x的左右节点所以距离+1
//距离现在节点 x 的距离为 i+1 的数量更新
}
if(l == 0 && r == 0) cnt[x][0] = 1;
return x;
}
int countPairs(TreeNode* root, int D) {
ans = tot = 0;
memset(cnt, 0, sizeof cnt);
dfs(root, D);
return ans;
}
};
###中序序列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JaSdV90p-1616578457391)(/Users/yangfan/Code/计算机相关基础/算法题整理.assets/image-20200806222648361.png)]
class Solution {
public:
/**
* 返回所求中序序列
* @param n int整型 二叉树节点数量
* @param pre int整型vector 前序序列
* @param suf int整型vector 后序序列
* @return int整型vector
前序:根左右
中序:左根右 :按中序 就 先处理左子树 后处理根,再处理 右子树
后序:左右根
*/
unordered_map<int, int> sufIndex;
vector<int> v;
void deal(int pl, int pr, vector<int>& pre, int sl, int sr, vector<int>& suf){
if(pl > pr) return;
if(pl == pr) {v.push_back(pre[pl]); return;}
int pos = sufIndex[pre[pl + 1]];
deal(pl + 1,pos + pl - sl + 1 ,pre, sl, pos, suf);
v.push_back(pre[pl]);
deal(pos + pl - sl + 2 ,pr ,pre, pos + 1,sr - 1 , suf);
}
vector<int> solve(int n, vector<int>& pre, vector<int>& suf) {
// write code here
for(int i = 0; i < suf.size(); i++){
sufIndex[suf[i]] = i;
}
deal(0, n-1, pre, 0, n-1, suf);
return v;
}
};
TOP K 问题
求数组中top3大的数
- 不让用stl
- 要求时间复杂度O(n)
/*
先冒泡排序,O(k^2)
b[0] < b[1] < b[2]
然后跟后面的数比较 O(n)
*/
int b[3];
int get_3rd(int a[], int n){
for(int i = 0; i < 3;i++) b[i] = a[i];
for(int i = 0; i < 3;i++)
for(int j = i + 1; j < 3;j++)
if(b[i] > b[j]) swap(b[i], b[j]);
if(n == 3) return b[0];
for(int i = 3; i < n; i++){
if(a[i] > b[2]){
b[0] = a[i];
swap(b[0], b[2]);
swap(b[0], b[1]);
}else if(a[i] > b[1]){
swap(b[0], b[1]);
b[1] = a[i];
}else if(a[i] > b[0]){
b[0] = a[i];
}
}
return b[0];
}
TOP K
O(n)
https://www.acwing.com/activity/content/problem/content/820/1/
面试题 17.14. 最小K个数
215. 数组中的第K个最大元素
//优先队列复杂度 时间 nlogk
//完整快排 nlogn
//利用快排思想
快排思想
#####前k小,前k大
//第k小
private static int SmallKquickSearch(int[] nums, int low, int high, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, low, high);
if (j == k) {
return nums[j];
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
return j > k? SmallKquickSearch(nums, low, j - 1, k): SmallKquickSearch(nums, j + 1, high, k);
}
//前k小
private static int[] quickSearch(int[] nums, int low, int high, int k) {
// 每快排切分1次,找到排序后下标为j的元素,如果j恰好等于k就返回j以及j左边所有的数;
int j = partition(nums, low, high);
if (j == k) {
return Arrays.copyOf(nums, j+1);
}
// 否则根据下标j与k的大小关系来决定继续切分左段还是右段。
return j > k? quickSearch(nums, low, j - 1, k): quickSearch(nums, j + 1, high, k);
}
// 快排切分,返回下标j,使得比nums[j]小的数都在j的左边,比nums[j]大的数都在j的右边。
private static int partition(int[] nums, int low, int high) {
int v = nums[low];
int i = low, j = high + 1;
while (true) {
while (++i <= high && nums[i] < v);
while (--j >= low && nums[j] > v);
if (i >= j) {
break;
}
int t = nums[j];
nums[j] = nums[i];
nums[i] = t;
}
nums[low] = nums[j];
nums[j] = v;
return j;
}
//返回第k大
private static int findTopK(int[] nums, int start, int end, int k) {
int low = start, high = end;
int tmp = nums[low];//枢轴点
while (low < high) {
while (low < high && nums[high] <= tmp) high--;
//高处遇到比tmp小的数,将它放在地位
nums[low] = nums[high];
while (low < high && nums[low] >= tmp) low++;
//低处遇到比tmp大的数,把它放到高位
nums[high] = nums[low];
}
nums[high] = tmp; //枢轴点归位
if (high == k -1) return tmp;
else if (high > k -1) return findTopK(nums, start, high - 1, k);
else return findTopK(nums,high + 1, end, k);
}
}
Acwing 786. 第k小的数
//利用快排的思想 去找第k小的数 !!!不能求重复数,需要首先去重
#include <iostream>
using namespace std;
const int N = 100010;
int q[N];
int quick_sort(int l, int r, int k)
{
if (l >= r) return q[l];
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j){
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
if (j - l + 1 >= k) return quick_sort(l, j, k);
else return quick_sort(j + 1, r, k - (j - l + 1));
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i ++ ) scanf("%d", &q[i]);
cout << quick_sort(0, n - 1, k) << endl;
return 0;
}
桶排序 求TOP K
class Solution {
public:
/*
首先On 得数组的最大最小值
开辟maxVal - minVal + 1的数组 用来统计数字(空间复杂度略高)
再次遍历数组 用num - minVal的差值为下标
然后倒序遍历 cnt 统计数字出现次数
O(2n + k) 时间复杂度
*/
int findKthLargest(vector<int>& nums, int k) {
int len = nums.size();
int maxVal = -1e9, minVal = 1e9;
for(auto& x:nums){
maxVal = max(x, maxVal);
minVal = min(x, minVal);
}
vector<int> bucket(maxVal - minVal + 1, 0);
for(auto& x:nums) bucket[x - minVal]++;
int cnt = 0;
for(int i = maxVal - minVal; i >= 0; i--){
if(cnt + bucket[i] >=k) return i + minVal;
cnt += bucket[i];
}
return -1;
}
};
众数/中位数
无序数组中找众数(出现次数大于数组长度一半)
用摩尔计数可以找出 前提众数必须存在
中位数
可以建一个k个值的堆Ok
将n-k个元素插入堆,并弹出堆顶 O(2* (n-k) *log k) 2代表两次操作
如果k非常小或者接近于n,算法可以达到On
如果k接近于n/2 那么就会O(nlogn)
背包问题
回文问题
技巧/智力题
###牛牛的字符反转
class Solution {
public:
/**
*
* @param n int整型 字符串长度n
* @param k int整型 循环右移次数k
* @return int整型
*/
int solve(int n, int k) {
// write code here
k = k % n;
if(n == 1 || k == 0) return 0;
if(n == 2) return 1; //
//k ==2 || k == n-2
if(k == 1 || k == n - 1 || k == 2 || k == n-2) return 2;
else return 3;
//string sub =
//1234567 每个位试试
//1-2 2次 n-2/ n-1 1次
}
};
牛牛排队
class Solution {
public:
/**
* 返回牛牛最终是从第几个门进入食堂吃饭的
* @param n int整型 代表门的数量
* @param a int整型vector 代表每个门外等待的人数
* @return int整型
*/
//先跑一轮 看看能不能进
//进不去 每个就再过 (a[i] - i)/n 圈 但是要向上取整
//t里面存着某个门 第几圈能进 记录最少圈 且最靠左的
int solve(int n, vector<int>& a) {
vector<int> t(n);
// write code here
int minn = 0x7f7f7f7f;
int pos = -1;
for(int i = 0; i < n; i++){
a[i] -= i;
if(a[i] <= 0) t[i] = 1;
else t[i] = 1 + (a[i] + n - 1) / n;
if(t[i] < minn){
minn = t[i];
pos = i;
}
}
return pos + 1;
}
};
模拟题
Protocol buffer 编码和解码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zN3ZNOuX-1616578457395)(/Users/yangfan/Code/计算机相关基础/算法题整理.assets/2378849F2A4EA77B92E52F02F5D1DA58.jpg)]
#include <iostream>
#include <unordered_map>
#include <string>
#include <cstring>
#include <iomanip>
using namespace std;
void encode(unsigned int n){
if(n < 128) {
if(n < 16) cout <<"0X0"<<hex<<n;
else cout <<"0X"<<hex<<n;
return;
}
while(n){
unsigned int tmp;
if(n >= 128){
tmp = (0x80 | (n & 0x7F));
cout<<"0X"<<setiosflags(ios::uppercase)<<hex<<tmp;//大写16进制数字
}else{
tmp = (0x00|(n & 0x7F));
if(tmp < 16) cout <<"0X0"<<hex<<tmp;
else cout <<"0X"<<hex<<tmp;
}
n >>= 7;
}
return;
}
unsigned int decode(string& x){
unsigned int u, res = 0;
int cnt = 0;
while(x.size()){
string tmp = x.substr(0,4);
//必须要依据格式输入 否则超索引范围
x = x.substr(4);
tmp = tmp.substr(2);
u = 0;
for(int i = 0; i < tmp.size(); i++){
if(tmp[i] >='0' && tmp[i] <= '9') u = u * 16 + tmp[i] - '0';
if(tmp[i] >='A' && tmp[i] <= 'F') u = u * 16 + tmp[i] - 'A' + 10;
}
unsigned int temp;
temp = (u & 0x7F);
for(int i = 1; i <= cnt; i++){
temp <<= 7;
}
res += temp;
cnt++;
}
return res;
}
int main(){
unsigned int n;
string x;
cin >> n >> x;
encode(n);
cout <<endl;
cout <<dec<<decode(x)<<endl;
return 0;
/*
999 - 0XE70X07
100 - 0X64
*/
}
数学题
##[254. 丢鸡蛋][https://www.lintcode.com/problem/drop-eggs/description]
//最终要从 x + x -1 + x-2 + .... + 1 上丢下来,所以这个和 >= n 即可
//那平均高度是多少呢 答案就是初始层数 x
int dropEggs(int n) {
// Write your code here
long long ans = 0;
int x = 0;
while (ans < n) {
x += 1;
ans += x;
}
return x;
}
素数问题
数组
class Solution {
public:
/**
* @param nums: A list of integers
* @return: A list of integers
*/
//找 后序遍历 num[i-1] < num[i] 的位置 再后序 找比num[i-1] 大的数
//交换,之后再反转[i ~end] 因为i之后的数是最大排列
//交换后 i到结尾 数字依然是递减的 nums[j-1] > nums[i-1] > nums[j+1]
vector<int> nextPermutation(vector<int> &nums) {
// write your code here
if (nums.size() <= 1) {
return nums;
}
int i = nums.size() - 1;
while (i > 0 && nums[i] <= nums[i - 1]) {
i--;
}
if (i) {
int j = nums.size() - 1;
while (nums[j] <= nums[i - 1]) {
j--;
}
swap(nums[i - 1], nums[j]);
}
reverse(nums.begin() + i, nums.end());
return nums;
}
};
//reverse是为了让i之后的数字变小 如果找比它大的数 那么就i == 0 的时候返回 -1;
链表
142. 环形链表 II
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VNQ9pRv3-1616578457401)(https://i.loli.net/2021/03/24/XbxwFvEJ2Dl1rIq.png)]
假设环外长度为 a,环长度为b,第一次相遇距离环入口长度为c
第一次相遇slow走的距离为a+c,fast的距离为a+n*b+c。由于fast走的距离是slow的两倍,所以
a+n*b+c = 2a+2c => a + c= n * b => a = (n-1)*b +b - c
如此一来,将slow放回head处。两人相同的步速,再次相遇就是链表的入口。
slow距入口长度为a,fast离入口为b-c。 a = (n-1)*b + b - c(n >= 1), 所以上述推理正确!
贪心
石头、剪刀、布II
class Solution {
public:
int Highestscore(int n, int p1, int q1, int m1, int p2, int q2, int m2) {
// write code here
int t1 = min(p1, q2), t2 = min(q1, m2), t3 = min(m1, p2);
//能赢分的
int ans = t1 + t2 + t3;
p1 -= t1, q2 -= t1;
q1 -= t2, m2 -= t2;
m1 -= t3, p2 -= t3;
p1 -= min(p1, p2);
q1 -= min(q1, q2);
m1 -= min(m1, m2);
//能打平的
ans -= (p1 + q1 + m1);
//只能输的
return ans;
}
};
STL
vector e[1000];
相当于开了1000个vector 容器
模板存储
// 快速排序算法模板
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l];
while (i < j)
{
do i ++ ; while (q[i] < x);
do j -- ; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
else break;
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
// 归并排序算法模板
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
if (q[i] < q[j]) tmp[k ++ ] = q[i ++ ];
else tmp[k ++ ] = q[j ++ ];
while (i <= mid) tmp[k ++ ] = q[i ++ ];
while (j <= r) tmp[k ++ ] = q[j ++ ];
for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}
// 高精度加法
// C = A + B, A >= 0, B >= 0
vector<int> add(vector<int> &A, vector<int> &B)
{
if (A.size() < B.size()) return add(B, A);
vector<int> C;
int t = 0;
for (int i = 0; i < A.size(); i ++ )
{
t += A[i];
if (i < B.size()) t += B[i];
C.push_back(t % 10);
t /= 10;
}
if (t) C.push_back(t);
return C;
}
// 高精度减法
// C = A - B, 满足A >= B, A >= 0, B >= 0
vector<int> sub(vector<int> &A, vector<int> &B)
{
vector<int> C;
for (int i = 0, t = 0; i < A.size(); i ++ )
{
t = A[i] - t;
if (i < B.size()) t -= B[i];
C.push_back((t + 10) % 10);
if (t < 0) t = 1;
else t = 0;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
// 高精度乘低精度
// C = A * b, A >= 0, b > 0
vector<int> mul(vector<int> &A, int b)
{
vector<int> C;
int t = 0;
for (int i = 0; i < A.size() || t; i ++ )
{
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
return C;
}
// 高精度除以低精度
// A / b = C ... r, A >= 0, b > 0
vector<int> div(vector<int> &A, int b, int &r)
{
vector<int> C;
r = 0;
for (int i = A.size() - 1; i >= 0; i -- )
{
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}
reverse(C.begin(), C.end());
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
// 一维前缀和
// S[i] = a[1] + a[2] + ... a[i]
// a[l] + ... + a[r] = S[r] - S[l - 1]
// 二维前缀和
// S[i, j] = 第i行j列格子左上部分所有元素的和
// 以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为 S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]
// 一维差分
// B[i] = a[i] - a[i - 1]
// 给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c
// 二维差分
// 给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:
// S[x1, y1] += c, S[x2 + 1, y1] -= c, S[x1, y2 + 1] -= c, S[x2 + 1, y2 + 1] += c
优先级队列 存储自定义数据结构
class Solution {
public:
struct A{
int x;
int i;
int j;
bool operator < (const A& a)const{
return x < a.x;
}
bool operator> (const A& a)const{//不加const会出错
return x > a.x;
}
/*
const对象不能调用类内非const函数,非const函数期望的指针是*this,
而传入的是const *this,类型不匹配
a是const对象,可以把const A& a写成A &a,后边不用加const(但是不规范),
所以需要指定函数为const
*/
};
priority_queue<A, vector<A>, greater<A>> minheap;
int longestIncreasingPath(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
if(!m || !n) return 0;
for(int i = 0; i < m;i++)
for(int j = 0; j < n;j++){
minheap.push({matrix[i][j], i ,j});
}
while(minheap.size()){
auto t = minheap.top();
minheap.pop();
cout << t.x<<" "<<t.i <<" "<<t.j<<endl;
}
return -1;
}
};
树,链表结构体定义
struct ListNode{
int val;
ListNode* next;
ListNode{int x} :val(x), next(nullptr) {}
};
struct TreeNode{
int val;
TreeNode *left, *right;
TreeNode(int x): val(x), left(nullptr), right(nullptr){}
}