消除重复、泛型算法

目录

一,消除重复

1,程序的组成结构

2,变化的层次

3,重复场景、消除手段、产物和条件

(1)数据变化

(2)类型变化

(3)行为变化

4,条件类型实例

(1)数据在某个范围内

(2)类型参数满足一元运算

(3)类型参数满足二元运算

(4)函数指针满足赋值运算

(5)函数指针满足关系条件

二,重构实例

1,数据变化

2,类型变化

3,行为变化

三,ACM模板

1,vector + 等价关系

2,vector + 全序关系

3,vector + 字典序

4,vector + 半群

力扣 265. 粉刷房子 II(min)

力扣 2003. 每棵子树内缺失的最小基因值(min)

力扣 2680. 最大或值(或)

力扣 1554. 只有一个不同字符的字符串

5,树 + 半群、幺半群


一,消除重复

1,程序的组成结构

程序可以细分为4个代码层次:数据+数据类型+基础运算+算法

PS:int、float、vector<int>、vector<float>、节点类型是int的二叉树、节点类型是float的二叉树就是6种不同的数据类型,更复杂的数据结构,也叫数据类型。

2,变化的层次

如果有2段代码包含重复的知识,那就需要消除重复。

消除重复,就是将软件中的变和不变分离,再重新组织在⼀起。

按照程序的层次,重复可以分为三个层次:数据变化、类型变化、行为变化

这3个变化层次,分别对应前3个代码层次,如果最高层次的算法不一样,那就不属于重复代码,而是无关代码了。

3,重复场景、消除手段、产物和条件

(1)数据变化

场景:2段代码的数据不同,数据类型和行为相同

消除重复的手段:数据参数化(提取函数)

产物:普通函数

编译条件:无

运行条件:数据在某个范围内

PS:这个数据范围可能等于该数据类型的全体范围,下同。

PS:数据在范围A内,函数可以正常运行结束,数据在范围B内,功能符合预期,B可能等于A也可能是A的真子集

(2)类型变化

场景:2段代码的数据和数据类型不同,行为相同

消除重复的手段:类型参数化(模板编程)

产物:泛型函数

编译条件:类型参数满足运算条件

运行条件:数据在某个范围内

PS:运算条件指的是该类型具有可能存在的一元运算和二元运算,下同。

(3)行为变化

场景:2段代码的数据、数据类型和行为都不同,但算法是相同的

消除重复的手段:行为数据化,进一步参数化(函数指针)

产物:泛型算法(带函数指针的泛型函数)

编译条件:类型参数满足运算条件,函数指针满足赋值运算条件

运行条件:数据在某个范围内,函数指针满足关系条件

PS:函数指针满足对应的关系条件,才能使得功能符合预期。这个关系可能包括二元关系、交换律、结合律等。

4,条件类型实例

(1)数据在某个范围内

int f(int x)
{
	return 10 / x;
}

条件:范围是x!=0

int f(int x)
{
	if (x == 0)return 0;
	return 10 / x;
}

条件:范围是全体int数(无条件)

(2)类型参数满足一元运算

template<typename T>
T opt(T a)
{
	return !a;
}

条件:T类型满足一元位运算非运算,假设运算结果是T2类型,那么T类型还满足一元运算赋值运算,从T2到T赋值。

PS:即使是从T到T的赋值,也不是所有类型都满足的。

template<typename T>
int opt(T a)
{
	return a.x;
}

条件:T类型满足,具有成员x

(3)类型参数满足二元运算

template<typename T>
int opt(T a, T b)
{
	return a + b;
}

条件:T类型满足加法运算

template<typename T>
int opt(T a)
{
	return a > T{ 0 };
}

条件:T类型满足用0构造这个一元运算,还满足比较大小这个二元运算,即序关系。

PS:所有的二元关系,都可以理解成满足二元运算

(4)函数指针满足赋值运算

template<typename A>
A f(A a, int n, A(*pfunc)(A, A, int))
{
	A x = pfunc(a, a, n);
	A y = pfunc(x, x, n);
	return y;
}

条件:A类型满足赋值运算

template<typename A, typename P>
A f(A a, int n, P pfunc)
{
	A x = pfunc(a, a, n);
	A y = pfunc(x, x, n);
	return y;
}

条件:A类型满足赋值运算,且A类型可以隐式转换成P的前2个参数类型,且int类型可以隐式转换成P的最后一个参数类型。

(5)函数指针满足关系条件

class SemiGroup
{
public:
	//枚举只去掉1个数(v.size()>1),剩下的数做p累积运算的结果
	template<typename T, typename Tfunc>
	static vector<T> allExceptOne(vector<T>& v, Tfunc p) {
		vector<T>left(v.size() - 1), right(v.size() - 1);
		T x = v[0];
		for (int i = 1; i < v.size(); i++)left[i - 1] = x, x = p(x, v[i]);
		x = v.back();
		for (int i = int(v.size()) - 2; i >= 0; i--)right[i] = x, x = p(v[i], x);
		vector<T>ans(1, right[0]);
		for (int i = 0; i < left.size() - 1; i++)ans.push_back(p(left[i], right[i + 1]));
		ans.push_back(left.back());
		return ans;
	}
};

条件:T类型上的p运算满足半群。

二,重构实例

1,数据变化

原代码:

void func()
{
	int a, b, c;
	int d = a * b + c * c * c;
	// ......
	int e, f, g;
	int h = e * f + g * g * g;
}

重构代码:

int opt(int a, int b, int c)
{
	return a * b + c * c * c;
}
void func()
{
	int a, b, c;
	int d = opt(a, b, c);
	// ......
	int e, f, g;
	int h = opt(e, f, g);
}

2,类型变化

原代码:

int opt(int a, int b, int c)
{
	return a * b + c * c * c;
}
int opt(float a, float b, float c)
{
	return a * b + c * c * c;
}

这个代码已经完成了第一层次的消除重复,还需要继续消除重复。

重构代码:

template<typename T>
int opt(T a, T b, T c)
{
	return a * b + c * c * c;
}

模板编程的更多内容,参考C++模板编程

3,行为变化

原代码:

template<typename A, typename N>
static inline A multiAdd(A a, N n, int p)
{
	if (n <= 1)return a;
	A ans = multiAdd<A, N>(a, n / 2, p);
	ans = (ans + ans) % p;
	if (n % 2)ans = (ans + a) % p;
	return ans;
}
template<typename A, typename N>
static inline A multiMulti(A a, N n, int p)
{
	if (n <= 1)return a;
	A ans = multiMulti<A, N>(a, n / 2, p);
	ans = (ans * ans) % p;
	if (n % 2)ans = (ans * a) % p;
	return ans;
}

这个代码已经完成了第一层次和第二层次的消除重复,还需要继续消除重复。

重构代码:

template<typename A, typename N>
static inline A aijiMulti(A a, N n, int p, A(*pfunc)(A, A, int))
{
	if (n <= 1)return a;
	A ans = aijiMulti<A, N>(a, n / 2, p, pfunc);
	ans = pfunc(ans, ans, p);
	if (n % 2)ans = pfunc(ans, a, p);
	return ans;
}
template<typename A>
static inline A opAdd(A x, A y, int p)
{
	return (x + y) % p;
}
template<typename A>
static inline A opMulti(A x, A y, int p)
{
	return (x * y) % p;
}
template<typename A, typename N>
static inline A multiAdd(A a, N n, int p)
{
	return aijiMulti(a, n, p, opAdd<A>);
}
template<typename A, typename N>
static inline A multiMulti(A a, N n, int p)
{
	return aijiMulti(a, n, p, opMulti<A>);
}

PS:这里给出的是数据和数据类型都有变化的场景,如果是数据有变化但是数据类型没有变化的场景,那就是基于普通函数(非目标函数)做同样的重构。

三,ACM模板

1,vector<T> + 等价关系

C ++版本

    //从vector(一维或2维)中删除所有的x
    template<typename T>
	static void deletAllX(vector<T>& v, T x)
	{
		for (int i = 0; i < v.size(); i++)if (v[i] == x)v.erase(v.begin() + i--);
	}
	template<typename T>
	static void deletAllX(vector<vector<T>>& v, T x)
	{
		for (auto& vi : v)deletAllX(vi, x);
	}
    //判断数组去掉相邻重复数之后有多少个不同的数
	template<typename T>//T可以是指针或迭代器,统计范围是[left,right)
	static int getDifNum(T left, T right)
	{
		if (left == right)return 0;
		int ans = 1;
		for (T it = left + 1; it != right; it++)ans += *it != *(it - 1);
		return ans;
	}
	template<typename T>
	static int getDifNum(vector<T>v)
	{
		return getDifNum(v.begin(), v.end());
	}

2,vector<T> + 全序关系

    //vector的择优合并
	template<typename T>
	static vector<T> mergeVector(const vector<T>& a, const vector<T>& b)
	{
		vector<T> ans;
		int i;
		for (i = 0; i < a.size() && i < b.size(); i++) {
			if (isGreater(a[i], b[i]))ans.push_back(a[i]);
			else ans.push_back(b[i]);
		}
		for (; i < a.size(); i++)ans.push_back(a[i]);
		for (; i < b.size(); i++)ans.push_back(b[i]);
		return ans;
	}
    template<typename T>
	static bool isGreater(T a, T b)
	{
		return a > b;
	}

3,vector<T> + 字典序

    //vector的字典序比较,v1<v2是true,v1>=v2是false
	template<typename T>
	static bool cmpVector(const vector<T>& v1, const vector<T>& v2)
	{
		for (int i = 0; i < v1.size() && i < v2.size(); i++)
		{
			if (v1[i] != v2[i])return v1[i] < v2[i];
		}
		return v1.size() < v2.size();
	}
	//vector的字典序排序
	template<typename T>
	static void sortVector(vector<vector<T>>& v)
	{
		sort(v.begin(), v.end(), cmpVector<T>);
	}

4,vector<T> + 半群

这里我限制了vectro长度至少为2,所以需要的是半群。

如果限制改成长度至少为1,则需要传入单位元,即需要的是幺半群。

//vector+半群
class VecSemiGroup
{
public:
	template<typename T>
	static vector<T> allExceptOneMin(vector<T>& v)
	{
		return allExceptOne(v, [](T a, T b) {return min(a, b); });
	}
	template<typename T>
	static vector<T> allExceptOneOr(vector<T>& v)
	{
		return allExceptOne(v, [](T a, T b) {return a | b; });
	}
private:
	//枚举只去掉1个数(v.size()>1),剩下的数做p累积运算的结果
	template<typename T, typename Tfunc>
	static vector<T> allExceptOne(vector<T>& v, Tfunc p) {
		vector<T>left(v.size() - 1), right(v.size() - 1);
		T x = v[0];
		for (int i = 1; i < v.size(); i++)left[i - 1] = x, x = p(x, v[i]);
		x = v.back();
		for (int i = int(v.size()) - 2; i >= 0; i--)right[i] = x, x = p(v[i], x);
		vector<T>ans(1, right[0]);
		for (int i = 0; i < left.size() - 1; i++)ans.push_back(p(left[i], right[i + 1]));
		ans.push_back(left.back());
		return ans;
	}
};

值得深思的是,我们说这个一个基于半群的泛型算法,其实说的是“如果p是一个满足半群的运算,那么allExceptOne求出来的就是枚举只去掉1个数,剩下的数做p累积运算的结果”,如果p不满足半群,那么allExceptOne求出来的就是枚举只去掉1个数,剩下的左边的数按左结合做p累积运算,右边的数按右结合做p累积运算,最终左右两边做p运算得到的结果。

也就是说,即使p不满足半群,这仍然是一个算法。力扣 1554. 只有一个不同字符的字符串 用的就是这个算法。

p不满足半群的情况下,求出来的结果的用法很有限,不像allExceptOneMin求出来的结果的数值有具体用法,力扣 1554. 只有一个不同字符的字符串中的p是一个哈希函数,allExceptOne求出来的结果仅仅用于比较2个结果是否相等。所以说,p不满足半群的用法属于额外的拓展用法,正常情况下p应该满足半群

力扣 265. 粉刷房子 II

假如有一排房子共有 n 幢,每个房子可以被粉刷成 k 种颜色中的一种。房子粉刷成不同颜色的花费成本也是不同的。你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。

每个房子粉刷成不同颜色的花费以一个 n x k 的矩阵表示。

例如,costs[0][0] 表示第 0 幢房子粉刷成 0 号颜色的成本;costs[1][2] 表示第 1 幢房子粉刷成 2 号颜色的成本,以此类推。
返回 粉刷完所有房子的最低成本 。

示例 1:

输入: costs = [[1,5,3],[2,9,4]]
输出: 5
解释: 
将房子 0 刷成 0 号颜色,房子 1 刷成 2 号颜色。花费: 1 + 4 = 5; 
或者将 房子 0 刷成 2 号颜色,房子 1 刷成 0 号颜色。花费: 3 + 2 = 5. 
示例 2:

输入: costs = [[1,3],[2,4]]
输出: 5
 

提示:

costs.length == n
costs[i].length == k
1 <= n <= 100
2 <= k <= 20
1 <= costs[i][j] <= 20
 

进阶:您能否在 O(nk) 的时间复杂度下解决此问题?

class Solution {
public:
	int minCostII(vector<vector<int>>& costs) {
		vector<int> ans = costs[0];
		for (int i = 1; i < costs.size(); i++) {
			vector<int>t = costs[i];
			auto min2 = AllExceptOneMin(ans);
			for (int i = 0; i < t.size(); i++)t[i] += min2[i];
			ans = t;
		}
		return *min_element(ans.begin(), ans.end());
	}
};

力扣 2003. 每棵子树内缺失的最小基因值(min)

有一棵根节点为 0 的 家族树 ,总共包含 n 个节点,节点编号为 0 到 n - 1 。给你一个下标从 0 开始的整数数组 parents ,其中 parents[i] 是节点 i 的父节点。由于节点 0 是  ,所以 parents[0] == -1 。

总共有 105 个基因值,每个基因值都用 闭区间 [1, 105] 中的一个整数表示。给你一个下标从 0 开始的整数数组 nums ,其中 nums[i] 是节点 i 的基因值,且基因值 互不相同 。

请你返回一个数组 ans ,长度为 n ,其中 ans[i] 是以节点 i 为根的子树内 缺失 的 最小 基因值。

节点 x 为根的 子树 包含节点 x 和它所有的 后代 节点。

示例 1:

输入:parents = [-1,0,0,2], nums = [1,2,3,4]
输出:[5,1,1,1]
解释:每个子树答案计算结果如下:
- 0:子树包含节点 [0,1,2,3] ,基因值分别为 [1,2,3,4] 。5 是缺失的最小基因值。
- 1:子树只包含节点 1 ,基因值为 2 。1 是缺失的最小基因值。
- 2:子树包含节点 [2,3] ,基因值分别为 [3,4] 。1 是缺失的最小基因值。
- 3:子树只包含节点 3 ,基因值为 4 。1是缺失的最小基因值。

示例 2:

输入:parents = [-1,0,1,0,3,3], nums = [5,4,6,2,1,3]
输出:[7,1,1,4,2,1]
解释:每个子树答案计算结果如下:
- 0:子树内包含节点 [0,1,2,3,4,5] ,基因值分别为 [5,4,6,2,1,3] 。7 是缺失的最小基因值。
- 1:子树内包含节点 [1,2] ,基因值分别为 [4,6] 。 1 是缺失的最小基因值。
- 2:子树内只包含节点 2 ,基因值为 6 。1 是缺失的最小基因值。
- 3:子树内包含节点 [3,4,5] ,基因值分别为 [2,1,3] 。4 是缺失的最小基因值。
- 4:子树内只包含节点 4 ,基因值为 1 。2 是缺失的最小基因值。
- 5:子树内只包含节点 5 ,基因值为 3 。1 是缺失的最小基因值。

示例 3:

输入:parents = [-1,2,3,0,2,4,1], nums = [2,3,4,5,6,7,8]
输出:[1,1,1,1,1,1,1]
解释:所有子树都缺失基因值 1 。

提示:

  • n == parents.length == nums.length
  • 2 <= n <= 105
  • 对于 i != 0 ,满足 0 <= parents[i] <= n - 1
  • parents[0] == -1
  • parents 表示一棵合法的树。
  • 1 <= nums[i] <= 105
  • nums[i] 互不相同。

思路:

2次dfs

第一次dfs,把每颗子树的所有节点的最小值算出来。

第二次dfs,根据父节点的最小缺失值,算出所有子节点的最小缺失值。

具体来说,子节点的最小缺失值是3个值的最小值,这3个分别是父节点的最小缺失值、父节点的值、所有兄弟节点及其子节点的最小值。


class Solution {
public:
	vector<int> smallestMissingValueSubtree(vector<int>& parents, vector<int>& nums) {
		int n = parents.size();
		sons.resize(n);
		mins.resize(n);
		ans.resize(n);
		for (int i = 1; i < parents.size(); i++)sons[parents[i]].push_back(i);
		getMin(nums, 0);
		map<int, int>m;
		for (auto x : nums)m[x]++;
		int minMiss = 1;
		while (m[minMiss])minMiss++;
		dfs(nums, 0, minMiss);
		return ans;
	}
private:
	int getMin(vector<int>& nums, int id) {
		int ans = nums[id];
		for (auto son : sons[id])ans = min(ans, getMin(nums, son));
		return mins[id] = ans;
	}
	void dfs(vector<int>& nums, int id, int miss) {
		ans[id] = miss;
		miss = min(miss, nums[id]);
		if (sons[id].size() <= 1) {
			for (auto son : sons[id])dfs(nums, son, miss);
			return;
		}
		vector<int>sonMin;
		for (auto son : sons[id])sonMin.push_back(mins[son]);
		auto otherMin = AllExceptOneMin(sonMin);
		for (int i = 0; i < sons[id].size(); i++) {
			dfs(nums, sons[id][i], min(miss, otherMin[i]));
		}
	}
private:
	vector<vector<int>>sons;
	vector<int>mins;
	vector<int>ans;
};

力扣 2680. 最大或值(或)

给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 k 。每一次操作中,你可以选择一个数并将它乘 2 。

你最多可以进行 k 次操作,请你返回 nums[0] | nums[1] | ... | nums[n - 1] 的最大值。

a | b 表示两个整数 a 和 b 的 按位或 运算。

示例 1:

输入:nums = [12,9], k = 1
输出:30
解释:如果我们对下标为 1 的元素进行操作,新的数组为 [12,18] 。此时得到最优答案为 12 和 18 的按位或运算的结果,也就是 30 。

示例 2:

输入:nums = [8,1,2], k = 2
输出:35
解释:如果我们对下标 0 处的元素进行操作,得到新数组 [32,1,2] 。此时得到最优答案为 32|1|2 = 35 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= k <= 15

思路:

k次操作一定会用完,而且一定会加在同一个数上,所以只需要枚举除了这个数之外,其他的数的或运算总结果。

或运算是环非域,但是这里只需要用到半群的结合性。

class Solution {
public:
	long long maximumOr(vector<int>& nums, int k) {
		if (nums.size() == 1)return nums[0] << k;
		auto x = AllExceptOneOr(nums);
		long long ans = 0;
		for (int i = 0; i < x.size(); i++)ans = max(ans, ((long long)nums[i] << k) | x[i]);
		return ans;
	}
};

力扣 1554. 只有一个不同字符的字符串

给定一个字符串列表 dict ,其中所有字符串的长度都相同。

当存在两个字符串在相同索引处只有一个字符不同时,返回 True ,否则返回 False 。

示例 1:

输入:dict = ["abcd","acbd", "aacd"]
输出:true
解释:字符串 "abcd" 和 "aacd" 只在索引 1 处有一个不同的字符。

示例 2:

输入:dict = ["ab","cd","yz"]
输出:false

示例 3:

输入:dict = ["abcd","cccc","abyd","abab"]
输出:true

提示:

  • dict 中的字符数小于或等于 10^5 。
  • dict[i].length == dict[j].length
  • dict[i] 是互不相同的。
  • dict[i] 只包含小写英文字母。

进阶:你可以以 O(n*m) 的复杂度解决问题吗?其中 n 是列表 dict 的长度,m 是字符串的长度。

思路:

枚举一个字符串去掉任意一个字符之后的哈希值。

即使自定义的哈希运算不满足结合律,也仍然可以利用半群模板达到效果,且性能达标。


long long myhash(long long x, long long y)
{
	return (x * x % 1000000007 * 97 + y * 11 + 5) % 1000000007;
}
class Solution:VecSemiGroup {
public:
	bool differByOne(vector<string>& dict) {
		vector<set<long long>>vs(dict[0].length());
		for (auto& s : dict) {
			auto v = hashs(s);
			for (int i = 0; i < vs.size(); i++) {
				if (vs[i].find(v[i]) != vs[i].end())return true;
				vs[i].insert(v[i]);
			}
		}
		return false;
	}
	vector<long long> hashs(string& s) {
		vector<long long>v(s.length());
		for (int i = 0; i < s.length(); i++)v[i] = s[i] - 'a';
		return allExceptOne(v, myhash);
	}
};

5,树 + 半群、幺半群

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 TreeSemiGroup
{
public:
	static map<TreeNode*, int> allNodeSum(TreeNode *root) {
		return allNodeMultiOptP(root, [](int a, int b) {return a + b; });
	}
protected:
	//枚举以任意节点作为根得到的树,该树所有节点按照中序做p累积运算的结果
	template<typename Tfunc>
	static map<TreeNode*, int> allNodeMultiOptP(TreeNode *root, const Tfunc &p) {
		map<TreeNode*, int> ans;
		if (!root)return ans;
		dfs(root, p, ans);
		return ans;
	}
private:
	template<typename Tfunc>
	static int dfs(TreeNode *root, const Tfunc &p, map<TreeNode*, int>&m)
	{
		int ans = root->val;
		if (root->left)ans = p(dfs(root->left, p, m), ans);
		if (root->right)ans = p(ans, dfs(root->right, p, m));
		return m[root] = ans;
	}
};
class TreeMonoid:public TreeSemiGroup
{
public:
	//枚举去掉1个节点和它的所有子节点,剩下的树按照中序做p累积运算的结果
	template<typename Tfunc>
	static map<TreeNode*, int> allExceptOne(TreeNode *root, Tfunc p, int e) {
		map<TreeNode*, int> allMulti = allNodeMultiOptP(root, p);
		map<TreeNode*, int> ans;
		dfs(root, p, e, e, allMulti, ans); // e是单位元素
		return ans;
	}
private:
	template<typename Tfunc>
	static void dfs(TreeNode *root, Tfunc p, int faLeft, int faRight, map<TreeNode*, int> &allMulti, map<TreeNode*, int> &ans) {
		ans[root] = p(faLeft, faRight);
		if (root->left) {
			int ans = root->val;
			if (root->right)ans = p(ans, allMulti[root->right]);
			dfs(root->left, p, faLeft, p(ans, faRight), allMulti, ans);
		}
		if (root->right) {
			int ans = root->val;
			if (root->left)ans = p(allMulti[root->left], ans);
			dfs(root->right, p, p(faLeft, ans), faRight, allMulti, ans);
		}
	}
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值