消除重复、泛型算法

博客围绕代码重复消除展开,介绍程序组成结构与变化层次,包括数据、类型和行为变化。阐述不同重复场景的消除手段、产物和条件,给出重构实例。还提供ACM模板,如vector与多种关系结合,以及多个力扣题目的分析,涉及粉刷房子、基因值、或值计算等。

目录

一,消除重复

1,程序的组成结构

2,变化的层次

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

(1)数据变化

(2)类型变化

(3)行为变化

4,条件类型实例

(1)数据在某个范围内

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

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

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

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

二,重构实例

1,数据变化

2,类型变化

3,行为变化

三,算法的体系结构

1,前言

2,数据的条件

3,数据结构的条件

4,运算的条件

5,最终分类维度

四,算法分类

1,前言

2,《线性表》+《等价运算+顺序累积运算》

3,《线性表》+《序运算+顺序累积运算》

4,《线性表》+《等价运算+序运算+合并运算》

5,《线性表》+《数值运算+等价运算+序运算+合并运算》

6,《线性表》+《顺序累积运算+逆序累积运算+合并运算》

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

7,《线性表》+ 《半群》

力扣 265. 粉刷房子 II

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

力扣 2680. 最大或值(或)

力扣 2906. 构造乘积矩阵

8,《二叉树》+《半群》 

9,《二叉树》+《幺半群》


一,消除重复

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

三,算法的体系结构

1,前言

我们考虑对所有的算法进行归纳和分类。

上文提到程序=数据+数据类型+基础运算+算法,这里,我们换个表述,基于数据和数据结构和运算需要满足的条件来对算法进行归纳和分类。

2,数据的条件

我们考察一个算法需要基于什么样的数据,常见的类型条件有:

(1)需要支持==比较运算,即具有等价关系的数据类型

(2)需要支持>或者>=比较运算,即具有序关系的数据类型

(3)需要支持累积运算

(4)需要支持某种数值运算,如需要支持x%10,从而支持分桶运算,从而支持桶排序算法

PS:并不是所有的数据都支持累积运算,比如基于有向图的很多算法,无论怎么抽象都没办法表示成累积运算。

3,数据结构的条件

我们考察一个算法需要基于什么样的数据结构,常见的数据结构有:

线性表、链表、高维线性表、无向图、有向图等

4,运算的条件

我们考察一个算法需要基于什么样的运算,常见的条件有

顺序累积运算、逆序累积运算、分治等

(1)顺序累积运算

以基于vector<T1>的运算f为例,假设T2 ans = f(vector<T1>& input),顺序累积运算需要的运算是:

T2 newAns = g(T2 ans, T1 x)

(2)逆序累积运算

以基于vector<T1>的运算f为例,假设T2 ans = f(vector<T1>& input),逆序累积运算需要的运算是:

T2 newAns = g(T1 x,T2 ans)

PS:顺序累积运算和逆序累积运算,并不一定等价于顺序遍历的累积运算和逆序遍历的累积运算,不过在很多特定场景下是可以这么对应去理解的。这个并非本文抽象的核心关注点,所以下文不再专门区分这2套概念。

(3)合并运算

以基于vector<T1>的运算f为例,假设T2 ans = f(vector<T1>& input),分治算法中的合并运算可能是:

T2 newAns = g(T2 ans1, T2 ans2),也可能是其他类似的形式

5,最终分类维度

需要支持==比较运算、需要支持>或者>=比较运算、需要支持累积运算、需要支持某种数值运算,其实也就是:

等价运算、序运算、累积运算、数值运算

所以我们把数据和运算这2个维度合并,最终只剩下数据结构、运算这2个维度。

数据结构有:线性表、链表、高维线性表、无向图、有向图等

运算有:等价运算、序运算、顺序累积运算、逆序累积运算、数值运算、合并运算、半群、幺半群、群等

四,算法分类

1,前言

一般的算法分类,都是基于核心思路来分类,这样的分类有助于我们在遇到一个问题时迅速想出用什么算法去求解。

我这里的分类,是更细致的分类,有助于我们探索第一性原理。

2,《线性表》+《等价运算+顺序累积运算》

示例:删除指定元素算法、去重计数算法

    //从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());
	}

3,《线性表》+《序运算+顺序累积运算》

示例:择优合并算法、字典序算法

    //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;
	}

    //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();
	}

4,《线性表》+《等价运算+序运算+合并运算》

示例:快速排序算法

5,《线性表》+《数值运算+等价运算+序运算+合并运算》

示例:桶排序内套一个快速排序

6,《线性表》+《顺序累积运算+逆序累积运算+合并运算》

示例:枚举只去掉1个数,剩下的左边的数按左结合做p累积运算,右边的数按右结合做p累积运算,最终左右两边做p运算得到的结果。

//vector+顺序累积运算+逆序累积运算
class VecAlg
{
public:
	//枚举只去掉1个数(v.size()>1),剩下的左边的数按左结合做p累积运算,右边的数按右结合做p累积运算,最终左右两边做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;
	}
};

PS:左边的数按左结合做p累积运算,就是顺序累积运算,右边的数按右结合做p累积运算,就是逆序累积运算

使用示例:

 力扣 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:VecAlg {
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);
	}
};

7,《线性表》+ 《半群》

这里我限制了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;
	}
};

注意到,这个代码和上一个代码,2个代码中的allExceptOne函数其实是一模一样的。

2个算法的区别就在于:

  • VecAlg中的p运算是支持顺序累积运算+逆序累积运算+合并运算,所以VecAlg算法是枚举只去掉1个数,剩下的左边的数按左结合做p累积运算,右边的数按右结合做p累积运算,最终左右两边做p运算得到的结果。
  • VecSemiGroup中的p运算是半群,即除了顺序累积运算+逆序累积运算+合并运算,还需要p运算满足结合律,所以VecSemiGroup算法是枚举只去掉1个数,剩下的数做p累积运算的结果。

以下是一些使用VecSemiGroup算法的例子。

力扣 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;
	}
};

力扣 2906. 构造乘积矩阵

给你一个下标从 0 开始、大小为 n * m 的二维整数矩阵 grid ,定义一个下标从 0 开始、大小为 n * m 的的二维矩阵 p。如果满足以下条件,则称 p 为 grid 的 乘积矩阵 :

  • 对于每个元素 p[i][j] ,它的值等于除了 grid[i][j] 外所有元素的乘积。乘积对 12345 取余数。

返回 grid 的乘积矩阵。

 

示例 1:

输入:grid = [[1,2],[3,4]]
输出:[[24,12],[8,6]]
解释:p[0][0] = grid[0][1] * grid[1][0] * grid[1][1] = 2 * 3 * 4 = 24
p[0][1] = grid[0][0] * grid[1][0] * grid[1][1] = 1 * 3 * 4 = 12
p[1][0] = grid[0][0] * grid[0][1] * grid[1][1] = 1 * 2 * 4 = 8
p[1][1] = grid[0][0] * grid[0][1] * grid[1][0] = 1 * 2 * 3 = 6
所以答案是 [[24,12],[8,6]] 。

示例 2:

输入:grid = [[12345],[2],[1]]
输出:[[2],[0],[0]]
解释:p[0][0] = grid[0][1] * grid[0][2] = 2 * 1 = 2
p[1][0] = grid[0][0] * grid[2][0] = 12345 * 1 = 12345. 12345 % 12345 = 0 ,所以 p[1][0] = 0
p[2][0] = grid[0][0] * grid[1][0] = 12345 * 2 = 24690. 24690 % 12345 = 0 ,所以 p[2][0] = 0
所以答案是 [[2],[0],[0]] 。

 

提示:

  • 1 <= n == grid.length <= 105
  • 1 <= m == grid[i].length <= 105
  • 2 <= n * m <= 105
  • 1 <= grid[i][j] <= 109
class Solution {
public:
	vector<vector<int>> constructProductMatrix(vector<vector<int>>& grid) {
		vector<int>v;
		for (int i = 0; i < grid.size(); i++) {
			for (int j = 0; j < grid[i].size(); j++) {
				v.push_back(grid[i][j] % 12345);
			}
		}
		auto ans = VecSemiGroup::allExceptOneMulti(v);
		vector<vector<int>>r = grid;
		for (int i = 0; i < r.size(); i++) {
			for (int j = 0; j < r[i].size(); j++) {
				r[i][j] = ans[i*r[0].size() + j] % 12345;
			}
		}
		return r;
	}
};

8,《二叉树》+《半群》 

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;
	}
};

9,《二叉树》+《幺半群》

//树上幺半群
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);
		}
	}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值