有营养的算法笔记(三)

有营养的算法笔记三)

    • 找出知晓密码的专家
    • 魔法数组
    • 最近公共祖先IV

找出知晓密码的专家

1.对应letecode链接
知晓秘密的专家
2.题目描述

有营养的算法笔记(三)_第1张图片

3.解题思路

这个题的解题思路就是这个并查集,首先我们需要将这个会议按照时间点来进行排序,然后将同一个时间点开会的人进行合并注意我们需要将知晓密码的专家弄成一个集合,不知道密码的专家弄成一个集合。请注意不知道秘密的集合再某个时刻开完会之后需要立即被解散,下一个时刻该开会开会该合并合并,最后我们将每个时刻该合并合并,该解散解散最后遍历整个数组看那些人知道知道密码,将其放入到数组当中返回即可。具体详情请看代码。

4.对应代码

class Solution {
  public:
    class UniFind {
      public:
        UniFind(int n, int fisrtPerson) {
            parent.resize(n);
            size.resize(n);
            help.resize(n);
            scret.resize(n);
            for (int i = 1; i < n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
            size[0] = 2; //0和firstPerson作为一个集合
            parent[fisrtPerson] = 0;
            scret[0] = true;
            //只要代表节点知道秘密那么整个集合当中所有的人就都知道秘密
        }
        int  findFather(int i) {
            int hi = 0;
            while (i != parent[i]) {
                i = parent[i];
                help[hi++] = i;
            }
            hi--;
            while (hi >= 0) {
                parent[help[hi]] = i; //路径压缩
                hi--;
            }
            return i;
        }

        void Union(int i, int j) {
            int fatheri = findFather(i);
            int fatherj = findFather(j);
            if (fatherj != fatheri) {
                //小的集合往大的集合合并
                if (size[fatheri] >= size[fatherj]) {
                    size[fatheri] += size[fatherj];
                    //合并集合
                    parent[fatherj] = fatheri;
                    scret[fatheri] = scret[fatheri] || scret[fatherj];
                } else {
                    size[fatherj] += size[fatheri];
                    parent[fatheri] = fatherj;
                    scret[fatherj] = scret[fatherj] || scret[fatheri];
                }
            }
        }
        bool know(int i) {

            return scret[findFather(i)];
            //注意这里需要重新寻找父亲因为他的父亲有可能指向了其它父亲
        }
        void isolate(int i) {
            parent[i] = i;
            //从一个集合当中分离开来
        }
      private:
        vector<int>parent;
        vector<int>size;
        vector<int>help;
        vector<bool>scret;//某个专家是否知道秘密
    };
    vector<int> findAllPeople(int n, vector<vector<int>>& meetings,
                              int firstPerson) {
        UniFind uf(n, firstPerson);
        auto cmp = [&](vector<int>& a, vector<int>& b) {
            return a[2] < b[2];
        };
        sort(meetings.begin(), meetings.end(), cmp);
        vector<int>help(n << 1);
        //将开会的专家放到一个数组里面方便后续进行合并
        help[0] = meetings[0][0];
        help[1] = meetings[0][1];
        int size = 2;
        int N = meetings.size();
        for (int i = 1; i < N; i++) {
            if (meetings[i][2] != meetings[i - 1][2]) {
                //到了下一个会议了
                shared(help, size, uf);
                help[0] = meetings[i][0];
                help[1] = meetings[i][1];
                size = 2;
            } else {


                help[size++] = meetings[i][0];
                help[size++] = meetings[i][1];
            }
        }
        shared(help, size, uf);
        vector<int>ans;
        for (int i = 0; i < n; i++) {

            if (uf.know(i)) {
                ans.push_back(i);
            }
        }
        return ans;
    }
    void shared(vector<int>& help, int size, UniFind& uf) {
        //开始进行合并集合,知道秘密的放在一组不知道秘密的再一组
        for (int i = 0; i < size; i += 2) {
            int first = help[i];
            int second = help[i + 1];
            uf.Union(first, second);
        }
        for (int i = 0; i < size; i++) {
            //不知道秘密的人需要解散
            if (!uf.know(help[i])) {
              
                uf.isolate(help[i]);
            }
        }
    }
};

魔法数组

1.本题为美团的笔试题,所以了没有笔试链接。在这里只给出代码和思路。

2.题目描述

arr数组长度为n, magic数组长度为m比如 arr = { 3, 1, 4, 5, 7 },如果完全不改变arr中的值,
那么收益就是累加和 = 3 + 1 + 4 + 5 + 7 = 20,magics[i] = {a,b,c} 表示arr[a~b]中的任何一个值都能改成c,并且每一种操作,都可以执行任意次,其中 0 <= a <= b < n。那么经过若干次的魔法操作,你当然可能得到arr的更大的累加和,返回arr尽可能大的累加和n <= 10^7 m <= 10^6 arr中的值和c的范围 <= 10^12。

3.解题思路

首先我们将magics数组进行排序,从小到大进行排序。为什么要这么干了,下面我们来举一个例子:[2,3,4],[1,4,6]按照题目的意思我们可以将[2,3]范围内的值改为4,但是下一个[1,4]范围内的数字改为6会覆盖掉前面的[2,3]范围内数字改成4.此时虽然覆盖了但是我6比你4要大,覆盖了也没关系,于是我们可以写出一个非常容易的暴力解,下面我们来看看整个暴力解如何来写

int maxSum1(vector<int>& nums, vector<vector<int>>& magics)
	{
		auto cmp = [&](vector<int>& a, vector<int>& b) {return a[2] < b[2]; };
		sort(magics.begin(), magics.end(), cmp);
		vector<int>help(nums.begin(), nums.end());
		int N = help.size();
		for (auto magic : magics)
		{
			int L = magic[0];
			int R = magic[1];
			int val = magic[2];
			//将整个范围内的值更新为val但是需要和原始数组的值比较,如果原始数组的值要大一些就不用更新了
			for (int i = L; i <= R; i++)
			{
				help[i] = max(help[i], val);
			}

		}
		int sum = 0;
		for (int x : help)
		{
			sum += x;
		}

		return sum;

	}

但是这样的时间复杂度是O(NN)的时间复杂度,每次更新都需要进行遍历才能更新完毕。其实这个枚举行为可以使用线段树进行优化将时间复杂度降低到O(NlogN).下面我们来看看使用线段树进行优化的版本

struct SegmentTree
	{
		vector<int>Max;
		vector<int>change;
		vector<bool>update;
	public:
	    SegmentTree(int size) {
			int N = size + 1;
			
			Max.resize(N << 2);
			change.resize(N << 2);
			update.resize(N << 2);
		}

		   void pushUp(int rt) {
			Max[rt] = max(Max[rt << 1], Max[rt << 1 | 1]);
		}

	        void pushDown(int rt, int ln, int rn) {
			if (update[rt]) {
				update[rt << 1] = true;
				update[rt << 1 | 1] = true;
				change[rt << 1] = change[rt];
				change[rt << 1 | 1] = change[rt];
				Max[rt << 1] = change[rt];
				Max[rt << 1 | 1] = change[rt];
				update[rt] = false;
			}
		}

		 void Update(int L, int R, int C, int l, int r, int rt) {
			if (L <= l && r <= R) {
				update[rt] = true;
				change[rt] = C;
				Max[rt] = C;
				return;
			}
			int mid = (l + r) >> 1;
			pushDown(rt, mid - l + 1, r - mid);
			if (L <= mid) {
				Update(L, R, C, l, mid, rt << 1);
			}
			if (R > mid) {
				Update(L, R, C, mid + 1, r, rt << 1 | 1);
			}
			pushUp(rt);
		}

		 int query(int L, int R, int l, int r, int rt) {
			if (L <= l && r <= R) {
				return Max[rt];
			}
			int mid = (l + r) >> 1;
			pushDown(rt, mid - l + 1, r - mid);
			int left = 0;
			int right = 0;
			if (L <= mid) {
				left = query(L, R, l, mid, rt << 1);
			}
			if (R > mid) {
				right = query(L, R, mid + 1, r, rt << 1 | 1);
			}
			return max(left, right);
		}

	};

	int maxSum2(vector<int>& nums, vector<vector<int>>& magics)
	{
		int N = nums.size();
		SegmentTree st(N);
		//进行排序
		auto cmp = [&](vector<int>& a, vector<int>& b) {return a[2] < b[2]; };
		sort(magics.begin(), magics.end(), cmp);
		for (auto magic : magics)
		{
			st.Update(magic[0] + 1, magic[1] + 1, magic[2], 1, N, 1);
			//进行更新
		}
		int ans = 0;
		for (int i = 0; i < N; i++) {
			ans += max(st.query(i + 1, i + 1, 1, N, 1), nums[i]);
		}
		return ans;
	
	}

对应总代码

#include
#include
#include
using namespace std;

class Solution
{
public:
	int maxSum1(vector<int>& nums, vector<vector<int>>& magics)
	{
		auto cmp = [&](vector<int>& a, vector<int>& b) {return a[2] < b[2]; };
		sort(magics.begin(), magics.end(), cmp);
		vector<int>help(nums.begin(), nums.end());
		int N = help.size();
		for (auto magic : magics)
		{
			int L = magic[0];
			int R = magic[1];
			int val = magic[2];
			//将整个范围内的值更新为val但是需要和原始数组的值比较,如果原始数组的值要大一些就不用更新了
			for (int i = L; i <= R; i++)
			{
				help[i] = max(help[i], val);
			}

		}
		int sum = 0;
		for (int x : help)
		{
			sum += x;
		}

		return sum;

	}
	struct SegmentTree
	{
		vector<int>Max;
		vector<int>change;
		vector<bool>update;
	public:
	    SegmentTree(int size) {
			int N = size + 1;
			
			Max.resize(N << 2);
			change.resize(N << 2);
			update.resize(N << 2);
		}

		   void pushUp(int rt) {
			Max[rt] = max(Max[rt << 1], Max[rt << 1 | 1]);
		}

	        void pushDown(int rt, int ln, int rn) {
			if (update[rt]) {
				update[rt << 1] = true;
				update[rt << 1 | 1] = true;
				change[rt << 1] = change[rt];
				change[rt << 1 | 1] = change[rt];
				Max[rt << 1] = change[rt];
				Max[rt << 1 | 1] = change[rt];
				update[rt] = false;
			}
		}

		 void Update(int L, int R, int C, int l, int r, int rt) {
			if (L <= l && r <= R) {
				update[rt] = true;
				change[rt] = C;
				Max[rt] = C;
				return;
			}
			int mid = (l + r) >> 1;
			pushDown(rt, mid - l + 1, r - mid);
			if (L <= mid) {
				Update(L, R, C, l, mid, rt << 1);
			}
			if (R > mid) {
				Update(L, R, C, mid + 1, r, rt << 1 | 1);
			}
			pushUp(rt);
		}

		 int query(int L, int R, int l, int r, int rt) {
			if (L <= l && r <= R) {
				return Max[rt];
			}
			int mid = (l + r) >> 1;
			pushDown(rt, mid - l + 1, r - mid);
			int left = 0;
			int right = 0;
			if (L <= mid) {
				left = query(L, R, l, mid, rt << 1);
			}
			if (R > mid) {
				right = query(L, R, mid + 1, r, rt << 1 | 1);
			}
			return max(left, right);
		}

	};

	int maxSum2(vector<int>& nums, vector<vector<int>>& magics)
	{
		int N = nums.size();
		SegmentTree st(N);
		//进行排序
		auto cmp = [&](vector<int>& a, vector<int>& b) {return a[2] < b[2]; };
		sort(magics.begin(), magics.end(), cmp);
		for (auto magic : magics)
		{
			st.Update(magic[0] + 1, magic[1] + 1, magic[2], 1, N, 1);
			//进行更新
		}
		int ans = 0;
		for (int i = 0; i < N; i++) {
			ans += max(st.query(i + 1, i + 1, 1, N, 1), nums[i]);
		}
		return ans;
	
	}
};


vector<vector<int>> getRandomMagic(int n, int m, int val)
{
	vector<vector<int>>magic(m, vector<int>(3));
	for (int i = 0; i < m; i++)
	{
		int a = rand() % n;
		int b = rand() % n;
		int c = rand() % val + 1;
		magic[i][0] = min(a, b);
		magic[i][1] = max(a, b);
		magic[i][2] = c;
	}
	return magic;

}
vector<int> generateRandomArray(int n, int value) {
	vector<int>arr(n);
	for (int i = 0; i < n; i++) {
		arr[i] = (int)(rand()% value) + 1;
	}
	return arr;
}


int main()
{
	vector<int>arr{ 3, 1, 4, 5, 7 };
	vector<vector<int>>magics{ {2, 4, 5}, {1, 3, 2} };
	cout << Solution().maxSum1(arr, magics) << endl;
	cout << Solution().maxSum2(arr, magics) << endl;
	int times = 10;
	int n = 30;
	int m = 15;
	int v = 100;
	for (int i = 0; i < times; i++)
	{
		int N = (int)(rand()% n) + 1;
		int M = (int)(rand() * m) + 1;
		vector<int>arr = generateRandomArray(N, v);
		vector<vector<int>>magics= getRandomMagic(N, M, v);
		cout << "进来" << endl;
		int ans1 = Solution().maxSum1(arr, magics);
		cout << "进来2" << endl;
		int ans2 = Solution().maxSum2(arr, magics);
		if (ans1 != ans2)
		{
			cout << "错了" << endl;
			return 0;
		}
	}
	cout << "对了" << endl;
	return 0;
}

最近公共祖先IV

1.对应letecode链接

最近公共祖先IV

2.题目描述

有营养的算法笔记(三)_第2张图片

有营养的算法笔记(三)_第3张图片
3.解题思路

在这里我们可以定义一个结构体来保存一个节点左树和右树是否找到了最低公共祖先如果找到了,直接返回即可。如果没找到看左树上找到了几个nodes当中的节点,右树上找到了几个nodes当中的节点。如果左树上面找到的节点数量加上右树上面找到的节点数量在加上自己是不是nodes当中的节点如果干好是nodes的长度那么如果当前这个节点就是最低公共祖先。否则则将以这个节点为头的找到了几个nodes当中的节点和是否找到了最低公共祖先打包返回给上一层。

4.对应代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
  public:
    struct Info {
        Info(int r, TreeNode* a)
            : remove(r), ans(a)
        {}
        int remove;//子树找到了几个节点在nodes当中
        TreeNode* ans; //记录最近公共祖先
    };
    TreeNode* lowestCommonAncestor(TreeNode* root, vector<TreeNode*>& nodes) {
        int N = nodes.size();
        unordered_set<int>cnt;
        for (auto x : nodes) {
            cnt.insert(x->val);
        }
        return  process(root, cnt, N)->ans;

    }
    Info* process(TreeNode* root, unordered_set<int>& cnt, int all) {
        if (root == nullptr) {
            return new Info(0, nullptr);
        }
        auto leftRet = process(root->left, cnt, all);
        //左边已经找到了最低公共祖先
        if (leftRet->ans) {
            return leftRet;
        }
        auto rightRet = process(root->right, cnt, all);
        //右边最近公共祖先
        if (rightRet->ans) {
            return rightRet;
        }
        int remove = leftRet->remove + rightRet->remove;
        if (cnt.count(root->val)) {
            remove += 1;
            cnt.erase(root->val);
        }
        TreeNode* ans = nullptr;
        //我的左孩子和右孩子加上我自己的数量刚好等于节点的数量说明当前节点就是
        //最近公共祖先
        if (remove == all) {
            ans = root;
        }
        delete leftRet;
        delete rightRet;
        return new Info(remove, ans);
    }
};

你可能感兴趣的:(有营养的算法笔记,leetcode,算法)