常见的数据结构及算法

1 线段树

在这里插入图片描述

1.1 建立线段树

1.2 单点修改

1.3 区间修改

1.4 区间查询

#include <iostream>
#include <cctype>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <cstdio>
#include <functional>
using namespace std;

class SegmentTree {
private:
	struct Node {
		int l, r, sum,lazy;
		Node() :l(-1), r(-1), sum(0),lazy(0) {}
	};
	const int MAX;
	vector<Node> tree;
	vector<int> data;//index from 1
	int n;
public:
	SegmentTree(vector<int>&nums) :MAX(4 * nums.size()), tree(vector<Node>(MAX)), data(nums),n(nums.size()) {
		data.insert(data.begin(), 0);
		build(1, 1, n);
	}
	//使用子节点更新父节点
	void pushUp(int k) {
		tree[k].sum = tree[k << 1].sum + tree[k << 1 | 1].sum;
	}
	//建立线段树,数据和树下标都从1起
	void build(int i, int l, int r) {
		tree[i].l = l, tree[i].r = r;
		//根节点
		if (l == r) {
			tree[i].sum = data[l];
			return;
		}
		//递归建子树
		int mid = l + (r - l)/2;
		build(i << 1, l, mid);
		build(i << 1 | 1, mid + 1, r);
		//使用子节点更新父节点
		pushUp(i);
	}

	//区间查询
	int query(int cur,int l, int r) {
		int L = tree[cur].l, R = tree[cur].r;
		//当前节点区间被目标区间完全包含,作为查询结果的一部分返回
		if (L >= l && R <= r)
			return tree[cur].sum;
		//当前节点与目标区间无交集
		if (L > r || R < l)return 0;
		//判断当前节点的子树是否与目标区间有交集
		int res = 0;
		//pushDown
		pushDown(cur);
		//计算左右子树
		if(tree[cur<<1].r>=l)res+=query(cur << 1, l, r);
		if(tree[cur<<1|1].l<=r)res+=query(cur << 1 | 1, l, r);
		return res;
	}

	//单点更新 将索引为k的位置改为val 要更新所有包含该点的区间的信息
	void change(int k, int val) {
		_change(1, k, val);
	}

	void _change(int cur, int k,int val) {
		int L = tree[cur].l, R = tree[cur].r;
		//更新根节点
		if (L == R) {
			tree[cur].sum = val;
			return;
		}
		//更新左右子树
		int mid = (L + R) / 2;
		if (mid >= k)_change(cur << 1, k, val);
		else _change(cur << 1 | 1, k, val);
		//更新当前节点的信息
		pushUp(cur);
	}

	//区间更新,指定区间内的每一个位置都+val
	void change_segment(int l, int r, int val) {
		_change_segment(1, l, r, val);
	}
	void _change_segment(int cur, int l, int r, int val) {
		int L = tree[cur].l, R = tree[cur].r;
		//当前区间完全被目标区间包含
		if (L >= l && R <= r) {
			tree[cur].sum += (R - L + 1)*val;
			//lazy表示当前节点已更新,但子树还未更新
			tree[cur].lazy += val;
			return;
		}
		int mid = (l + r) / 2;
		//pushDown
		pushDown(cur);
		if (L <= mid)_change_segment(cur << 1, l, r, val);
		if (R > mid)_change_segment(cur << 1 | 1, l, r, val);
		pushUp(cur);
	}

	//下推标记
	void pushDown(int cur) {
		if (tree[cur].lazy) {
			//下推标记
			tree[cur << 1].lazy += tree[cur].lazy;
			tree[cur << 1 | 1].lazy += tree[cur].lazy;
			//更新子节点的信息
			tree[cur << 1].sum += (tree[cur << 1].r - tree[cur << 1].l)*tree[cur].lazy;
			tree[cur << 1 | 1].sum += (tree[cur << 1 | 1].r - tree[cur << 1 | 1].l)*tree[cur].lazy;
			//clear
			tree[cur].lazy = 0;
		}
	}
};
int main() {
	vector<int> vec = { 1,2,3,4,5 };
	SegmentTree ta(vec);
	int a, b,c;
	char ch;
	while (1) {
		cin >>ch;
		if (ch == 'q') {//query
			cin >> a >> b;
			cout << ta.query(1, a, b) << endl;
		}
		else if (ch == 'c') {//change
			cin >> a >> b;
			ta.change(a, b);
		}
		else if (ch == 's') {//change_segment
			cin >> a >> b >> c;
			ta.change_segment(a, b, c);
		}
		
	}
	system("pause");
}

1.5 树状数组

在这里插入图片描述

1.5.1 树状数组模板

class TreeArray{
		//index from 1
        vector<int> data;
        int n;
        vector<int> c;
    public:
        TreeArray(vector<int>&nums):data(nums),n(nums.size()),c(vector<int>(n+1,0)){
            data.insert(data.begin(),0);
            for(int i=1;i<=n;i++)
                add(i,data[i]);
            cout<<endl;
        }

        int getSum(int i){
            int copy=i;
            int res=0;
            while(i>0){
                res+=c[i];
                i-=lowbit(i);                
            }
            return res;
        }
		//换值操作:直接修改值
        void update(int i,int val){
            int add = val - data[i];
            data[i]=val;
            while(i<=n){
                c[i]+=add;
                i+=lowbit(i);
            }
        }
		//加值操作:向某一位置加上一个值
        void add(int i,int val){
            while(i<=n){
                c[i]+=val;
                i+=lowbit(i);
            }
        }
        int lowbit(int x){
            return x&(-x);
        }

    };

1.5.2 树状数组的应用: 求树组中的逆序对

class Solution {
    class TreeArray{
        vector<int> tree;
        int n;

        int lowbit(int x){return x&(-x);}
    public:
        TreeArray(int _n): n(_n),tree(_n+1){

        }
        int getSum(int i){
            int res=0;
            while(i>0){
                res+=tree[i];
                i-=lowbit(i);
            }
            return res;
        }
        void add(int i,int val){
            while(i<=n){
                tree[i]+=val;
                i+=lowbit(i);
            }
        }
    };
public:
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        // 离散化
        vector<int> copy = nums;
        sort(copy.begin(),copy.end());
        vector<int> index(nums.size(),0);
        for(int i=0;i<nums.size();i++)
            index[i]=lower_bound(copy.begin(),copy.end(),nums[i])-copy.begin()+1;
        
        // 先放置下标在后面的数
        TreeArray treeArray(n);
        int res=0;
        for(int i=n-1;i>=0;i--){
            res+=treeArray.getSum(index[i]-1);
            treeArray.add(index[i],1);
        }
        return res;
    }
};

2 二叉搜索树

2.1 建树

2.2 插入节点

2.3 删除节点

leetcode 450. 删除二叉搜索树中的节点

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(!root)return NULL;
        // root->val != key
        if(root->val<key)root->right = deleteNode(root->right,key);
        else if(root->val>key)root->left = deleteNode(root->left,key);
        else {
            if(!root->right)return root->left;
            if(!root->left)return root->right;
            // find minValue of right subTree
            TreeNode * cur = root->right;
            while(cur->left)cur=cur->left;
            // swap value
            root->val = cur->val;
            cur->val = key;
            // delete again
            root->right=deleteNode(root->right,key);
        }
        return root;
    }
};

3 红黑树


4 堆

4.1 大顶堆与升序排序

class MaxHeap{
private:
    vector<int> data;
    int n;
public:
    MaxHeap(vector<int>&arr): data(arr),n(arr.size()){}
    void sink(int i){
        // 2*n+1, 2*n+2
        while(2*i+1<n){
            i=2*i+1;
            if(i+1<n&&data[i+1]>data[i])++i;
            if(data[(i-1)>>1]<data[i])
                swap(data[(i-1)>>1],data[i]);
        }
    }
    void makeHeap(){
        for(int i=(n-1)/2;i>=0;i--)sink(i);
    }
    void heapSort(){
        while(n>0){
            swap(data[0],data[--n]);
            sink(0);
        }
    }
    void print(){
        for(auto d:data)cout<<d<<" ";
        cout<<endl;
    }
};

int main() {
    vector<int> nums={1,3,5,10,9,7,6,2,8};
    MaxHeap heap(nums);
    heap.makeHeap();
    heap.heapSort();
    heap.print();
}


5 哈希、有序哈希

5.1 链地址法

5.2 LRU缓存

leetcode 146. LRU缓存机制

class LRUCache {
    list<pair<int,int>> data;//<key,value>
    unordered_map<int,list<pair<int,int>>::iterator> dict;//<key,pos>
    int cap;
public:
    LRUCache(int capacity) {
        cap=capacity;
    }
    
    int get(int key) {
        // 不存在
        if(!dict.count(key))return -1;

        auto pos = dict[key];
        pair<int,int> p = *pos;
        // 先删除
        data.erase(pos);
        // 再加入
        data.push_front(p);
        dict[key]=data.begin();

        // return
        return p.second;
    }
    
    void put(int key, int value) {
        // 已存在
        if(dict.count(key)){
            // 先删除
            data.erase(dict[key]);
        }
        // 不存在,且满了
        else if(data.size()==cap){
            // 先删除最旧的数据
            dict.erase(data.back().first);
            data.pop_back();
        }
        // 加入新的数据
        data.push_front({key,value});
        dict[key]=data.begin();
    }
};

5.3 LFU缓存

leetcode 460. LFU缓存

class LFUCache {
    struct Node{
        int key,value,freq;
        Node(int k,int v,int f):key(k),value(v),freq(f){}
    };
    int minFreq=0,cap;
    unordered_map<int,list<Node>::iterator> keysDict;
    unordered_map<int,list<Node>> freqDict;
public:
    LFUCache(int capacity) {
        cap=capacity;
    }
    
    int get(int key) {
        // 不存在
        if(!keysDict.count(key)){
            return -1;
        }

        auto whichNode = keysDict[key];
        auto node = *whichNode;
        // 先删除
        auto &whichList=freqDict[node.freq];
        whichList.erase(keysDict[key]);

        // 再装入
        freqDict[node.freq+1].push_front({node.key,node.value,node.freq+1});
        keysDict[key]=freqDict[node.freq+1].begin();

        // 更新freq
        if(whichList.empty()){
            freqDict.erase(node.freq);
            if(minFreq == node.freq)minFreq+=1;
        }
        return node.value;
    }
    
    void put(int key, int value) {
        // cap==0
        if(cap==0)return;

        Node newNode(key,value,1);
        int newMinFreq=1;

        // 存在
        if(keysDict.count(key)){
            auto whichNode = keysDict[key];
            auto node = *whichNode;
            // 先删除
            auto & whichList = freqDict[node.freq];
            whichList.erase(whichNode);
            newMinFreq = minFreq;
            // 更新新的,等待装入
            newNode.freq=node.freq+1;
            // 更新minFreq
            if(whichList.empty()){
                freqDict.erase(node.freq);
                if(newMinFreq == node.freq)newMinFreq+=1;
            }
        }
        // 满了且不存在
        else if(keysDict.size()==cap && !keysDict.count(key)){
            auto old = freqDict[minFreq].back();
            // 删除最旧的
            freqDict[minFreq].pop_back();
            keysDict.erase(old.key);
            if(freqDict[minFreq].empty())freqDict.erase(minFreq);
        }
        // 装入
        freqDict[newNode.freq].push_front(newNode);
        keysDict[key] = freqDict[newNode.freq].begin();
        minFreq = newMinFreq;
    }
};

/**
 * Your LFUCache object will be instantiated and called as such:
 * LFUCache* obj = new LFUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

6 并查集

6.1 模板

class UnionFind{
private:
    vector<int> father;
public:
    UnionFind(int n):father(n){
        for(int i=0;i<n;i++)father[i]=i;
    }
    int find(int x){
        return x==father[x]?x:father[x]=find(father[x]);
    }
    void connect(int x,int y){
        int fx=find(x),fy=find(y);
        father[fx]=fy;
    }
    bool isConnected(int x,int y){
        return find(x)==find(y);
    }
    static void test(){
        cout<<"\n************* UnionFind Test ******************\n";
        UnionFind uf(5);
        uf.connect(1,3);
        uf.connect(3,4);
        cout<<uf.isConnected(3,4)<<endl;
    }
};

6.2 应用

leetcode 947. 移除最多的同行或同列石头

class Solution {
    vector<int> p;
    void connect(int i,int j){
        int x = find(i);
        int y = find(j);
        p[y]=x;
    }
    int find(int i){
        if(i!=p[i])p[i]=find(p[i]);
        return p[i];
    }
public:
    int removeStones(vector<vector<int>>& stones) {
        p.resize(20000);
        for(int i=0;i<20000;i++)
            p[i]=i;
        for(auto s:stones)
            connect(s[0],s[1]+10000);
        set<int> dict;
        for(auto s:stones)
            dict.insert(find(s[0]));
        return stones.size()-dict.size();
    }
};

7 拓扑排序

7.1 入度排序

leetcode 207. 课程表

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        vector<vector<int>> adj(numCourses);
        vector<int> degree(numCourses,0);
        for(auto it:prerequisites){
            adj[it[0]].push_back(it[1]);
            ++degree[it[1]];
        }

        queue<int> q;
        for(int i=0;i<numCourses;i++)
            if(degree[i]==0)q.push(i);

        while(!q.empty()){
            int front=q.front();
            q.pop();
            numCourses--;

            for(auto to:adj[front])
                if(--degree[to]==0)q.push(to);
        }
        
        
        return numCourses==0;

    }
};

8 背包问题

8.1 0-1背包

n个物品放入容量为V的容器内,每个物品有自己的体积和价值,每个物品只能选择0次或1次,问容器内最多能装下多少价值的物品?

8.1.1 基础的两层循环

leetcode 1049. 最后一块石头的重量II

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        // sum
        int sum=0;
        for(auto stone:stones)sum+=stone;
        int V =sum/2;

        // bag
        int len = stones.size();
        vector<vector<int>> dp(len+1,vector<int>(V+1,0));
        for(int i=1;i<=len;i++){
            for(int j=0;j<=V;j++){
                if(j<stones[i-1])dp[i][j]=dp[i-1][j];
                else dp[i][j]=max(dp[i-1][j],dp[i-1][j-stones[i-1]]+stones[i-1]);
            }
        }
        return sum-2*dp[len][V];
    }
};

8.1.2 空间优化

leetcode 1049. 最后一块石头的重量II
二维数组优化为一维数组,注意倒着打表;

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        // sum
        int sum=0;
        for(auto stone:stones)sum+=stone;
        int V =sum/2;

        // bag
        vector<int> dp(V+1,0);
        for(auto s:stones){
            for(int j=V;j>=s;j--)
                dp[j]=max(dp[j],dp[j-s]+s);
        }
        return sum-2*dp[V];
    }
};

8.2 无限背包(完全背包)

物品可取无数次。

        vector<int> dp(V+1,0);
        for(auto item:items){
            for(int j=item;j<=V;j++)
                dp[j]=max(dp[j],dp[j-item]+item);
        }

8.3 多维背包

leetcode 474. 一和零

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));

        for(auto &str:strs){
            int cnt0 = count(str.begin(),str.end(),'0');
            int cnt1= str.size()-cnt0;

            for(int j=m;j>=cnt0;j--)
                for(int k=n;k>=cnt1;k--)
                    dp[j][k]=max(dp[j][k],dp[j-cnt0][k-cnt1]+1);
        }
        return dp[m][n];
    }
};

8.4 刚好装满的背包(组合数、物品数量最小数)

leetcode 322. 零钱兑换

class Solution {
    const int MAX = 0x3f3f3f3f;
    public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,MAX);//money cnt
        dp[0]=0;
        for(auto coin:coins)
            for(int j=coin;j<=amount;j++)
                dp[j]=min(dp[j],dp[j-coin]+1);

        return dp[amount]==MAX?-1:dp[amount];
    }
};

leetcode 518. 零钱兑换II

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        vector<int> dp(amount+1,0);
        dp[0]=1;
        for(auto c:coins)
            for(int j=c;j<=amount;j++)
                dp[j]+=dp[j-c];
        return dp[amount];
    }
};

leetcode 377. 组合总数IV

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<unsigned long long> dp(target+1,0);
        dp[0]=1;
        
        for(int j=1;j<=target;j++)
            for(auto n:nums)
                if(j>=n)dp[j]+=dp[j-n];
        return dp[target];
    }
};

8.5 多重背包(物品数量有限制)

leetcode LCP25 古董键盘

class Solution {
private:
    /* 计算组合数 */
    int C(int m, int n) {
        if ((n == 0) || (n == m))
            return 1;
        int ret = 1;
        for (int i = 1; i <= n; ++i) {
            ret *= (m - i + 1);
            ret /= i;
        }
        return ret;
    }
public:
    int keyboard(int k, int n) {
        vector<long long> dp(n + 1, 0);
        /* 按键0次则得出唯一的一种内容: 空 */
        dp[0] = 1;
        /* 遍历26个字母(26种货物) */
        for (int i = 0; i < 26; ++i) {
            /* 遍历背包的容量(从大到小) */
            for (int j = n; j >= 1; --j) {
                /* 放入x个当前字母 */
                for (int x = 1; x <= k; ++x) {
                    if (x > j)
                        continue;
                    dp[j] += C(j, x) * dp[j - x];
                    dp[j] %= 1000000007;
                }
            }
        }
        return dp.back();
    }
};

9 二叉树的非递归遍历

void Traversal(TreeNode* root) {
    stack<TreeNode*> stk;
    TreeNode *p = root, *prev = nullptr;
    while (p || !stk.empty()) {
        while (p) {
            // ① 前序遍历
            stk.push(p);
            p = p->left;
        }
        p = stk.top();
        
        // if (!p->right || p->right != prev) ② 中序遍历;
        
        if (p->right && p->right != prev) p = p->right;
        else {
            // ③ 后序遍历
            stk.pop();
            prev = p;
            p = nullptr;
        }
    }
}

另一种先序方法

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        if(!root)return res;
        stk.push(root);
        while(!stk.empty()){
            TreeNode * top = stk.top();
            stk.pop();
            res.push_back(top->val);
            if(top->right)stk.push(top->right);
            if(top->left)stk.push(top->left);
        }
        return res;
    }
};

另一种后序:改前序为“根右左”然后反转。

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        if(!root)return res;
        stk.push(root);
        while(!stk.empty()){
            TreeNode * top = stk.top();
            stk.pop();
            res.push_back(top->val);
            if(top->left)stk.push(top->left);
            if(top->right)stk.push(top->right);
        }
        reverse(res.begin(),res.end());
        return res;
    }
};

另一种中序:能向左走就向左走,没路了就从栈中取一个值并向右走。

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        stack<TreeNode*> stk;
        while(root || !stk.empty()){
            if(root){
                stk.push(root);
                root=root->left;
            }
            else{
                TreeNode * top = stk.top();
                stk.pop();
                res.push_back(top->val);
                root = top->right;
            }
        }
        return res;
    }
    
};

10 二分查找

10.1 正常的二分查找

 int search(vector<int>& nums, int target) {
     int l=0,r=nums.size()-1;
     while(l<=r){
         int mid = l + (r-l)/2;
         if(nums[mid]==target)return mid;
         else if(nums[mid]>target)r=mid-1;
         else l=mid+1;
     }
     return -1;
 }

10.2 lower_boundupper_bounder

剑指offer. 在排序数组中查找数字(出现的次数)

int lower_bound(vector<int> &nums,int target){
    if(target>nums[nums.size()-1])return nums.size();
    int l=0,r=nums.size()-1;
    while(l<r){
        int mid = l+(r-l)/2;
        if(nums[mid]<target)
            l=mid+1;
        else 
            r=mid;
    }
    return l;
}

int upper_bound(vector<int> &nums,int target){
    if(target>=nums[nums.size()-1])return nums.size();
    int l=0,r=nums.size()-1;
    while(l<r){
        int mid = l+(r-l)/2;
        if(nums[mid]>target)
            r=mid;
        else 
            l=mid+1;
    }
    return r;
}

10.3 l<rl<=r

题解

  • l<=r:在循环体内部查找元素;
  • l<r:在循环体内部排除元素,退出时的唯一元素就是查找结果;

10.4 mid=(l+r)>>1mid=(l+r+1)>>1

  • 将区间划分为[l,mid][mid+1,r],使用mid=(l+r)>>1;
  • 将区间划分为[l,mid-1][mid,r],使用mid=(l+r+1)>>1;

10.5 rank()

int rank(vector<int>&nums,int target){
    int l=0,r=nums.size()-1;
    while(l<=r){
        int mid = l+(r-l)/2;
        if(nums[mid]==target)return mid;
        else if(nums[mid]<target)l=mid+1;
        else r=mid-1;
    }
    return l;
}
    

10.6 在旋转数组中查找数字

leetcode 33. 搜索旋转排序数组

int search(vector<int>& nums, int target) {
    int l=0,len=nums.size(),r=len-1;
    while(l<=r){
        int mid = l+(r-l)/2;
        if(target==nums[mid])return mid;
        // l--mid is sorted
        if(nums[l]<= nums[mid]){
            if(target>=nums[l] && nums[mid]>target)r=mid-1;
            else l=mid+1;
        }
        else{ // mid--r is sorted
            if(target>nums[mid] && target<=nums[r])l=mid+1;
            else r=mid-1;
        }
    }
    return -1;
}

剑指offer 旋转数组的最小数字

class Solution {
public:
    int minArray(vector<int>& numbers) {
        if(numbers.empty()) return 0;
        int l=0,r=numbers.size()-1;
        while(l<r) {
            int mid = (l+r)>>1;   
             if(numbers[r] > numbers[mid]) {
                r = mid;
            }
            else if(numbers[r]<numbers[mid]){
                l=mid+1;
            }
            else r--;
        }
        return numbers[l];
    }
};

11 dp

11.1 区间dp

先讨论[i,i],[i,i+1]时的简单情况,然后用小区间数据辅助计算大区间的数据,相当于倒推。

leetcode 312. 戳气球

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        nums.insert(nums.begin(),1);
        nums.push_back(1);
        int len = nums.size();
        vector<vector<int>> dp(len,vector<int>(len,0));
        for(int l=2;l<len;l++){// length of []
            for(int i=0;i+l<len;i++){// start from i,end at i+l
                for(int j=i+1;j<i+l;j++)// j BOOMs at last
                    dp[i][i+l]=max(dp[i][i+l],dp[i][j]+dp[j][i+l]+nums[i]*nums[j]*nums[i+l]);
            }
        }
        return dp[0][len-1];
    }
};

12 KMP

看不懂,硬记就行了。

leetcode 214. 最短回文串

class Solution{
public:
    string shortestPalindrome(string s) {
        string r_s = s;
        reverse(r_s.begin(),r_s.end());
        string str = s+"#"+r_s;
        int n = str.size();
        vector<int> next(n+1,0);
        for(int i=0,j=next[0]=-1;i<n;next[++i]=++j){
            while(~j && str[i]!=str[j])j=next[j];
        }
        return r_s.substr(0, r_s.size()-next[n])+s;
    }
};

13 Manacher

其本质仍是中心扩展算法,但是做了更多的优化;

leetcode 214. 最短回文串

class Solution{
public:
    string shortestPalindrome(string s) {
        string ms = "#";
        for(auto ch:s){
            ms+=ch;
            ms+="#";
        }

        int len = ms.size();
        vector<int> p(len,0);
        int maxRight=0,center=0;
        int maxLen = 0,start=0;

        for(int i=0;i<len;i++){
            if(i<maxRight){
                int mirror = 2*center - i;
                p[i]=min(maxRight-i,p[mirror]);
            }
            int left = i-(1+p[i]);
            int right= i+(1+p[i]);
            while(left>=0 && right<len && ms[left--]==ms[right++])++p[i];
            if(i+p[i]>maxRight){
                maxRight=i+p[i];
                center=i;
            }
            if(p[i]>maxLen){
                start = (i-p[i])/2;
                if(start==0)
                    maxLen = p[i];
            }
        }
        string add = s.substr(maxLen);
        reverse(add.begin(),add.end());
        return add+s;
    }
};

14 TrieTree

leetcode 208. 实现Trie(前缀树)

class Trie {
    struct Node{
        char val;
        bool isEnd;
        Node* next[26];
        Node(){
            for(int i=0;i<26;i++)next[i]=NULL;
        }
        Node(char val_,bool isEnd_=false):val(val_),isEnd(isEnd_){
            for(int i=0;i<26;i++)next[i]=NULL;
        }
    };
    Node root;
public:
    /** Initialize your data structure here. */
    Trie() {
        
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Node * cur = &root;
        for(auto ch:word){
            if(cur->next[ch-'a']==NULL)cur->next[ch-'a']=new Node(ch);
            cur=cur->next[ch-'a'];
        }
        cur->isEnd=true;
    }
    
    /** Returns if the word is inå the trie. */
    bool search(string word) {
        Node * cur = &root;
        for(auto ch:word){
            if(cur->next[ch-'a']==NULL)return false;
            cur=cur->next[ch-'a'];
        }
        return cur->isEnd;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Node *cur = &root;
        for(auto ch:prefix){
            if(cur->next[ch-'a']==NULL)return false;
            cur=cur->next[ch-'a'];
        }
        for(int i=0;i<26;i++)
            if(cur->next[i])return true;
        return cur->isEnd;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

leetcode 588. 设计内存文件系统

class FileSystem {
    class TrieTree{
    public:
        TrieTree(string name){
            this->name = name;
        }
        // 当前文件(夹)的名字
        string name;
        unordered_map<string,TrieTree*> dict;
        // 文件信息
        bool isFile=false;
        string content;
    };
    TrieTree * root;

    // 相同的操作,到达某一个路径,不存在则创建
    TrieTree* reach(string path){
        TrieTree * cur = root;
        stringstream ss(path);
        string dir;
        while(getline(ss,dir,'/')){
            if(dir.empty())continue;
            if(!cur->dict.count(dir)){
                cur->dict[dir]=new TrieTree(dir);
            }
            cur=cur->dict[dir];
        }
        return cur;
    }
public:
    FileSystem() {
        root = new TrieTree("");
    }
    // 释放内存
    ~FileSystem(){
        free(root);
    }
    void free(TrieTree * cur){
        for(auto &it:cur->dict){
            free(it.second);
        }
        delete cur;
    }
    
    vector<string> ls(string path) {
        vector<string> res;
        TrieTree * cur = root;
        stringstream ss(path);
        string dir;
        // 不存在就返回空
        while(getline(ss,dir,'/')){
            if(dir.empty())continue;
            if(cur->dict.count(dir)){
                cur=cur->dict[dir];
            }
            else return {};
        }
        // 是文件就返回文件名
        if(cur->isFile)return {cur->name};
        // 文件夹则返回目录下所有内容
        for(auto &it:cur->dict)res.push_back(it.first);
        sort(res.begin(),res.end());
        return res;
    }
    
    void mkdir(string path) {
        reach(path);
    }
    
    void addContentToFile(string filePath, string content) {
        TrieTree * cur = reach(filePath);
        cur->isFile=true;
        cur->content += content;
    }
    
    string readContentFromFile(string filePath) {
        TrieTree * cur = reach(filePath);
        return cur->content;
    }
};

/**
 * Your FileSystem object will be instantiated and called as such:
 * FileSystem* obj = new FileSystem();
 * vector<string> param_1 = obj->ls(path);
 * obj->mkdir(path);
 * obj->addContentToFile(filePath,content);
 * string param_4 = obj->readContentFromFile(filePath);
 */

15 回溯

leetcode 40. 组合总数II

class Solution {
    // memthod1
    // 借助类变量完成回溯,较为简单明了。数组中的值是正序的。
    // 一次递归选择一个值,这个值可能是后面的任意一个值,全部遍历一边。
    vector<vector<int>> res;
    vector<int> path;
    void backtrack(vector<int>&candidates,int target,int pos){
        if(target==0){
            res.push_back(path);
            return;
        }
        for(int i=pos;i<candidates.size() && target>=candidates[i];i++){
            if(i>pos && candidates[i]==candidates[i-1])continue;
            // choose
            path.push_back(candidates[i]);
            backtrack(candidates,target-candidates[i],i+1);
            path.pop_back();
        }
    }

    // memthod2
    // 直接借助返回值完成回溯,数组中的值是倒序的。
    vector<vector<int>> backtrack2(vector<int>&candidates,int target,int pos){
        if(target==0)return {{}};
        vector<vector<int>> res;
        for(int i=pos;i<candidates.size() && target>=candidates[i];i++){
            if(i>pos && candidates[i]==candidates[i-1])continue;
            vector<vector<int>> subRes = backtrack2(candidates,target-candidates[i],i+1);
            for(auto vec:subRes){
                vec.push_back(candidates[i]);
                res.push_back(vec);
            }
        }
        return res;
    }

    // method3
    // 每个位置选与不选两种情况,因每次只考虑当前位置导致去重较为麻烦,某些不去重的题更实用。
    set<vector<int>> dict;
    void backtrack3(vector<int> &candidates,int target,int pos){
        if(target==0){
            if(!dict.count(path))
                res.push_back(path);
            dict.insert(path);
            return;
        }
        if(pos==candidates.size())return;
        // choose pos
        if(target>=candidates[pos]){
            path.push_back(candidates[pos]);
            backtrack3(candidates,target-candidates[pos],pos+1);
            path.pop_back();
        }
        // do not choose
        backtrack3(candidates,target,pos+1);
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        // method1
        //backtrack(candidates,target,0);
        //return res;

        // method2
        //return backtrack2(candidates,target,0);

        // method3
        backtrack3(candidates,target,0);
        return res;
    }
};

16. 快排

pivot的选择:

  • 选取第一个元素;
  • 选取最后一个元素;
  • 选取第一个、中间、最后一个元素的中位数:该方法性能一般较好。
  • 随机选取数组中某个值:性能较好,但random()并不便宜。

16.1 单向扫描法实现partition

int partition1(vector<int> s, int left, int right) {
	int pivot = s[left];
	int i = left ;
	for (int j = left+1; j <= right; j++) {
		if (s[j] <= pivot) {
			i++;
			if (i != j)swap(&s[i], &s[j]);
		}
	}
	swap(&s[left], &s[i]);
	return i;
}

16.2 双向扫描法实现partition(哨兵法)

void swap(int *a, int *b) {
	int tmp = *a; *a = *b; *b = tmp;
}
int partition2(vector<int> s, int left, int right) {
	int pivot = s[left];
	int i = left, j = right;
	while (i < j) {
		while (i<j&&s[j]>pivot)j--;
		while (i < j&&s[i] <= pivot)i++;
		if (i < j)swap(&s[i], &s[j]);
	}
	swap(&s[left], &s[i]);
	return i;
}

16.3 双向扫描法实现partition(挖坑法)

int partition3(vector<int> s, int left, int right) {
	int pivot = s[left];
	int i = left, j = right;
	while (i < j) {
		while (i<j && s[j]>pivot)j--;
		if (i < j)s[i++] = s[j];
		while (i < j&&s[i] <= pivot)i++;
		if (i < j)s[j--] = s[i];
	}
	s[i] = pivot;
	return i;
}

16.4 partition三路切分

假设数组的值仅有0,1,2三种取值,需要将全部0放到最左边,全部2放最右边,全部1放中间,可通过以下步骤实现:

  • 获取数组长度len,初始化i=0,low=0,high=len-1
  • 从两端向中间扫描,同时从左向右扫描;
  • 可以理解为low的左边全是0i指向当前数值,high的右边全是2;只要把全部的0移到左边,全部的2移到右边,那么剩下的1就自然全在中间了;
  • i位置上是1i右移;
  • 位置上是0,交换ilow位置,同时都右移;
  • i位置上是2,交换ihigh位置,high左移,但i不右移,因为交换后i位置可能是0,就还需要一次交换;
  • 完成后low指向第一个1high指向第一个2,如需要确切的划分位置可利用。
#include <iostream>
#include <stack>
#include <string>
#include <vector>
#include <algorithm>
#include <map>
#include <queue>
#include <stdio.h>
#include <list>
#include <set>

using namespace std;

void partitionThreeWay(vector<int> &s, int left, int right) {
	int i = left , low = left, high = right;
	while (i <= high) {
		if (s[i] == 1)i++;
		else if (s[i] == 0)swap(&s[i++], &s[low++]);
		else swap(&s[i], &s[high--]);
	}
	//输出内容
	for (int t = left; t <= right; t++) {
		if (t == low)cout << low << " ";
		else if (t == i)cout << i << " ";
		else cout << "_ ";
	}
	cout << endl;
	for (int t = left; t <= right; t++) {
		cout << s[t] << " ";
	}
	cout << endl;
}
int main()
{
	vector<int> s = { 0,1,2,2,1,0,1,2,0 };
	partitionThreeWay(s,0, s.size() - 1);
	system("pause");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值