数据结构与算法题

二分及变形

文章目录

基础二分

整数二分 算法模板
// 整数二分算法模板
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};
    }
};

背包问题

####lintcode 724. 最小划分 子集之差最小

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){}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值