算法题3

1. 三角形最小路径和

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

例如,给定三角形:

[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。

分析:自下而上动态规划,建立一个大小为n的help矩阵,用来保存走到当前层各个位置的最短路径,然后一层层向上累积。

int minimumTotal(vector<vector<int>>& triangle) {//三角形最小路径和
	int n = triangle.size();
	if (n == 0) return 0;
	vector<int> help(n,0);
	help.assign(triangle[n - 1].begin(), triangle[n - 1].end());
	for (int i = n - 2; i >= 0; --i) {
		for (int j = 0; j < i + 1; ++j) {
			help[j] = triangle[i][j] + (help[j] > help[j + 1] ? help[j + 1] : help[j]);
		}
	}
	return help[0];
}

2. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

输入: nums = [1,2,3]
输出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]

分析:
方法一:
若数组元素个数为n,则共有 2 n 2^n 2n个可能子集。然后可以通过 0 0 0~ 2 n − 1 2^n-1 2n1的二进制是否为1来选数字。

方法二:
开始为[],然后依次复制现有子集并添加最新元素。如:

{[]}->{[],[1]}->{[],[1],[2],[1,2]}....

方法三:
回溯法,可以在当前值选择要或者不要,然后回溯。

2.子集II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。

输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

分析:先排序,然后重复的元素打包个数为n,然后在已存在的子集基础上,给每个选择加0个,…,n个当前元素。

vector<vector<int>> subsetsWithDup(vector<int>& nums) {//子集II
	vector<vector<int>> result;
	int n = nums.size();
	if (n == 0) return result;
	int i = 0;
	vector<int> empty;
	result.push_back(empty);
	std::sort(nums.begin(), nums.end());
	while (i<n)
	{
		int num = 1;
		int key = i + 1;
		while (key<n && nums[i]==nums[key])
		{
			++key;
			++num;
		}
		int len = result.size();
		for (int j = 1; j <= num; ++j) {
			for (int k = 0; k < len; ++k) {
				vector<int> tem = result[k];
				for (int m = 0; m < j; ++m) {
					tem.push_back(nums[i]);
				}
				result.push_back(tem);
			}
		}
		i = key;
	}
	return result;
}

3. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

分析:

若假设首尾不相连,则采用动态规划设计help矩阵,help[i]为help[i-2]或help[i-3]中最大的与num[i]相加。help[i] = nums1[i] + (help[i - 3] > help[i - 2] ? help[i - 3] : help[i - 2]);

若首尾相连,则首先令num[0]=0,按上述规则运行一遍,再令num[n-1]=0,按上述规则运行一遍,取最大值。

int rob(vector<int>& nums) {//打家劫舍II
	int n = nums.size();
	if (n <= 0) return 0;
	if (n == 1) return nums[0];
	if (n == 2) return nums[1]>nums[0] ? nums[1] : nums[0];
	vector<int> help(n, 0);
	vector<int> nums1(n, 0);
	int result = 0;
	int tem = 0;

	nums1.assign(nums.begin(), nums.end());
	nums1[0] = 0;
	for (int i = 0; i < n; ++i) {
		if (i - 3 >= 0) {
			help[i] = nums1[i] + (help[i - 3] > help[i - 2] ? help[i - 3] : help[i - 2]);
		}
		else if (i - 2 >= 0) {
			help[i] = nums1[i] + help[i - 2];
		}
		else {
			help[i] = nums1[i];
		}
	}
	tem = help[n - 1] > help[n - 2] ? help[n - 1] : help[n - 2];
	if (tem > result) result = tem;
	
	nums1.assign(nums.rbegin(), nums.rend());
	nums1[0] = 0;
	for (int i = 0; i < n; ++i) {
		if (i - 3 >= 0) {
			help[i] = nums1[i] + (help[i - 3] > help[i - 2] ? help[i - 3] : help[i - 2]);
		}
		else if (i - 2 >= 0) {
			help[i] = nums1[i] + help[i - 2];
		}
		else {
			help[i] = nums1[i];
		}
	}
	tem = help[n - 1] > help[n - 2] ? help[n - 1] : help[n - 2];
	if (tem > result) result = tem;
	return result;
}

4. 复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

输入: “25525511135”
输出: [“255.255.11.135”, “255.255.111.35”]

分析:递归进行,每次递归记录剩余的str以及分割数。

判断条件:

  1. 若刚好切完,则加入结果集。
  2. 若剩余的不够切,或过多,return。
  3. 若切下数字超过255,continue。
  4. 若切下数字为0,但个数过多[00,000],continue。
  5. 若切下数字不为0,但首字母为0[01,001],continue。
void restoreIpAddressesHelp(string s, int split, string &add, vector<string> &result) {
	int len = s.size();
	if (len == 0 && split == 0) {
		result.push_back(add);
		return;
	}
	if (len < split || len>3 * split) return;
	for (int i = 1; i <= 3; ++i) {
		if (s.size() < i) continue;
		string cur = s.substr(0, i);
		string tem = add;
		int num = atoi(cur.c_str());
		if (num > 255) continue;
		if (num == 0 && cur.size() > 1) continue;
		if (num > 0 && cur[0] == '0') continue;
		if (!add.empty()) cur = "." + cur;
		add += cur;
		string next = "";
		if (s.size() > i) next = s.substr(i);
		restoreIpAddressesHelp(next, split - 1, add, result);
		add = tem;
	}
}

vector<string> restoreIpAddresses(string s) {//复原IP地址
	vector<string> result;
	if (s.empty()) return result;
	string add = "";
	restoreIpAddressesHelp(s, 4, add, result);
	return result;
}

5. 搜索旋转排序数组 II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true

输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

分析:用mid与left比较,然后再判断是否在最左或最右。若有重复,接着判断mid==left的情况。

bool search(vector<int>& nums, int target) {//搜索旋转排序数组 II
	int n = nums.size();
	if (n <= 0) return false;
	int left = 0;
	int right = n - 1;
	while (left<=right){
		int mid = left + (right - left) / 2;
		if (nums[mid] == target) return true;
		if (nums[mid] > nums[left]) {
			if (target >= nums[left] && target < nums[mid]) right = mid - 1;
			else left = mid + 1;
		}
		else if (nums[mid] < nums[left]) {
			if (nums[mid] < target&&target <= nums[right]) left = mid + 1;
			else right = mid - 1;
		}
		else {
			++left;
		}
	}
	return false;
}

6. 分隔链表

给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。

你应当保留两个分区中每个节点的初始相对位置。

输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5

分析:首先遍历一遍,将小于x的节点挑选出重新组成一个链表,然后与原链表相连。注意可以新设计两个头结点来方便处理第一个节点小于x的情况。

ListNode* partition(ListNode* head, int x) {//分隔链表
	if (!head) return head;
	ListNode *lessHead = new ListNode(0);
	ListNode *otherHead = new ListNode(0);
	otherHead->next = head;
	ListNode *last = otherHead;
	ListNode *lessCur = lessHead;
	ListNode *cur = head;
	while (cur){
		if (cur->val < x) {
			lessCur->next = cur;
			lessCur = cur;
			last->next = cur->next;
			cur = cur->next;

		}
		else {
			last->next = cur;
            last = last->next;
			cur = cur->next;
		}
	}
	lessCur->next = otherHead->next;
	cur = lessHead->next;
	delete lessHead;
	delete otherHead;
	return cur;
}

7. 格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。

输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
对于给定的 n,其格雷编码序列并不唯一。
例如,[0,2,3,1] 也是一个有效的格雷编码序列。
00 - 0
10 - 2
11 - 3
01 - 1

输入: 0
输出: [0]
解释: 我们定义格雷编码序列必须以 0 开头。
给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 20 = 1。
因此,当 n = 0 时,其格雷编码序列为 [0]。

分析:
方法一:
递归进行,在每一位上先递归,然后取反,再递归。

void grayCodeHelp(int &n, int k, vector<int> &result) {
	if (k == 0) {
		result.push_back(n);
		n = n ^ 1;
		result.push_back(n);
		return;
	}
	grayCodeHelp(n, k - 1, result);
	int i = 1 << k;
	n = n ^ i;
	grayCodeHelp(n, k - 1, result);
}

vector<int> grayCode(int n) {//格雷编码
	vector<int> result;
	if (n == 0) {
		result.push_back(0);
		return result;
	}
	int a = 0;
	int k = n - 1;
	grayCodeHelp(a, k, result);
	return result;

}

方法二:
每增加一位,就是在上一位基础上,在前面加0,然后取反序加1.

8. 不同的二叉搜索树 II

给定一个整数 n,生成所有由 1 … n 为节点所组成的二叉搜索树。

输入: 3
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1            3     3      2      1  
    \       /     /      / \      \  
     3     2     1      1   3      2  
    /     /       \                 \  
   2     1         2                 3  

分析:
递归构建,当i为中心节点时,分别递归左右的数。然后左子树与右子树的结果排列组合。

vector<TreeNode*> generateTreesHelp(int start, int end) {
	vector<TreeNode*> result;
	if (start > end) {
		result.push_back(NULL);
		return result;
	}
	for (int i = start; i <= end; ++i) {
		vector<TreeNode*> left = generateTreesHelp(start, i - 1);
		vector<TreeNode*> right = generateTreesHelp(i + 1, end);
		for (int j = 0; j < left.size(); ++j) {
			for (int k = 0; k < right.size(); ++k) {
				TreeNode *cur = new TreeNode(i);
				cur->left = left[j];
				cur->right = right[k];
				result.push_back(cur);
			}
		}
	}
	return result;
}

vector<TreeNode*> generateTrees(int n) {// 不同的二叉搜索树 II
	vector<TreeNode*> ans;
	if (n == 0) {
		return ans;
	}
	ans = generateTreesHelp(1, n);
	return ans;
}

9. 验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

分析:
方法一:后序遍历,每次返回左右子树的最大与最小值以及子树是否满足二叉搜索数。

方法二:定义一个全局变量表示上一个节点的大小,然后使用中序遍历,依次比上一个节点大。注意要给一个变量判断是否为第一个节点,若为第一个节点直接赋值。

bool isValidBSTHelp(TreeNode* root, int &last_value, bool &first) {
	if (root == NULL) {
		return true;
	}
	if (!isValidBSTHelp(root->left, last_value, first))
	{
		return false;
	}
	if (first == false && root->val <= last_value) {
		return false;
	}
	if (first == true) {
		first = false;
	}
	last_value = root->val;
	return isValidBSTHelp(root->right, last_value, first);
}

bool isValidBST(TreeNode* root) {//验证二叉搜索树
	int last = 0;
	bool first = true;
	return isValidBSTHelp(root, last, first);
}

方法三:设置一个子树的最大最小范围,左子树的最大值为当前节点,右子树的最小值为当前节点。

bool isValidBSTHelp(TreeNode* root, int little, int great) {
	if (root == NULL) {
		return true;
	}
	if (root->val > great || root->val < little) {
		return false;
	}
	return isValidBSTHelp(root->left, little, root->val) && isValidBSTHelp(root->right, root->val, great);

}

bool isValidBST(TreeNode* root) {//验证二叉搜索树
	return isValidBSTHelp(root, INT32_MIN, INT32_MAX);
}

10. Union-Find算法(计算联通子图个数)

计算一个图有几个联通子图。

分析:
1.首先构建一个数组father,若father[x]=x,则代表组号为x。否则,father[x]表示x的父节点,x组号与其父节点组号相同。
2.每次的流程为,若存在边则union两个节点的组号,使其组号相同。
3.find的作用为寻找x的组号。并使x的所有父节点直接指向带有组号的根节点。
4.最终计算出根节点的数量即可。

int find(int x, vector<int> &father) {//找组号
	int f = x;
	while (f!=father[f])
	{
		f = father[f];
	}
	while (x!=father[x])
	{
		int tem = father[x];
		father[x] = f;
		x = tem;
	}
	return f;
}

void connect(int p, int q, vector<int> &father) {//将p与q组合并
	int fp = find(p, father);
	int fq = find(q, father);
	if (fp != fq) {
		father[fp] = fq;
	}
}

bool canVisitAllRooms(vector<vector<int>>& rooms) {//钥匙和房间
	int n = rooms.size();
	vector<int> father(n, 0);
	for (int i = 0; i < n; ++i) {
		father[i] = i;
	}
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < rooms[i].size(); ++j) {
			connect(i, rooms[i][j], father);
		}
	}
	for (int i = 0; i < n; ++i) {
		find(i, father);
	}
	set<int> team(father.begin(), father.end());
	if (team.size() == 1) {
		return true;
	}
	return false;
}

11. 找到最终的安全状态(判断哪些节点走到吸收态)

分析:
方法一:
1.首先选出最终是吸收态的节点。
2.设计一个反向图rgraph,保存到j的所有节点。
3.然后依次剔除graph中到吸收态节点的边,若此时出现吸收态,则该节点也为安全状态。
4.设计一个队列queue保存将要剔除到该节点边的吸收态节点。设计一个数组array保存各个节点状态。

int findIter(int x, vector<int> data) {
	for (int i = 0; i < data.size(); ++i) {
		if (data[i] == x) {
			return i;
		}
	}
	return -1;
}
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {//找到最终的安全状态
	vector<int> result;
	int n = graph.size();
	vector<vector<int>> cgraph(n, vector<int>(0,0));
	vector<vector<int>> rgraph(n, vector<int>(0,0));
	vector<bool> label(n, false);
	queue<int> safe;
	for (int i = 0; i < n; ++i) {
		if (graph[i].size() == 0) {
			safe.push(i);
		}
		for (int j = 0; j < graph[i].size(); ++j) {
			cgraph[i].push_back(graph[i][j]);
			rgraph[graph[i][j]].push_back(i);
		}
	}
	while (!safe.empty()){
		int cur = safe.front();
		safe.pop();
		label[cur] = true;
		for (int i = 0; i < rgraph[cur].size(); ++i) {
			int index = findIter(cur, cgraph[rgraph[cur][i]]);
			cgraph[rgraph[cur][i]].erase(cgraph[rgraph[cur][i]].begin() + index);
			if (cgraph[rgraph[cur][i]].empty()) {
				safe.push(rgraph[cur][i]);
			}
		}
	}
	for (int i = 0; i < n; ++i) {
		if (label[i] == true) {
			result.push_back(i);
		}
	}
	return result;
}

方法二:

DFS深度优先遍历。white-gray-black(判断是否成环)

1.while表示还没遍历过。gray表示正在遍历。black表示遍历结束。
2.若遍历过程中发现邻居节点是gray的,表示成环了。直接返回false。若遍历完邻居后没有成环,则将该节点变为black,并返回true。

bool graphDFS(int x, vector<vector<int>> graph, vector<int> &color) {
	if (color[x] > 0) return color[x] == 2;
	color[x] = 1;
	for (int i = 0; i < graph[x].size(); ++i) {
		if (color[graph[x][i]] == 2) continue;
		if (color[graph[x][i]] == 1 || graphDFS(graph[x][i],graph,color) == false) return false;
	}
	color[x] = 2;
	return true;
}

vector<int> eventualSafeNodes2(vector<vector<int>>& graph) {//找到最终的安全状态2
	int n = graph.size();
	vector<int> color(n, 0);
	vector<int> result;
	for (int i = 0; i < n; ++i) {
		if (graphDFS(i, graph, color) == true) {
			result.push_back(i);
		}
	}
	return result;
}

12. 判断二分图(DFS变形)

给定一个无向图graph,当这个图为二分图时返回true。

如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。

graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释:
无向图如下:
0----1
|  |
|  |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。

示例 2:
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false
解释:
无向图如下:
0----1
| \ |
| \ |
3----2
我们不能将节点分割成两个独立的子集。

分析:
1.深度优先遍历DFS,设置一个color数组,0表示还没遍历过,1表示一组,-1表示一组。
2.每次判断组号是否相同。若不同返回false。

bool isBipartiteHelp(const vector<vector<int>> &graph, vector<int> &color, int x, int co) {
	if (color[x] != 0) return color[x] == co;
	color[x] = co;
	for (int i = 0; i < graph[x].size(); ++i) {
		if (isBipartiteHelp(graph, color, graph[x][i], -co) == false) return false;
	}
	return true;
}


bool isBipartite(vector<vector<int>>& graph) {//判断二分图
	int n = graph.size();
	vector<int> color(n, 0);
	int co = 1;
	for (int i = 0; i < n; ++i) {
		if (color[i] == 0 && isBipartiteHelp(graph, color, i, co) == false) return false;
	}
	return true;
}

13. 情侣牵手

N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。

人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。

这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

分析:
1.构建一个position数组,保存每个人的位置。
2.依次判断相邻两个人是否不满足需求,不满足则从position中取出parter位置,然后交换,然后更新position。

int minSwapsCouples(vector<int>& row) {//情侣牵手
	int n = row.size();
	vector<int> position(n, 0);
	vector<int> arr;
	arr.assign(row.begin(), row.end());
	int result = 0;
	for (int i = 0; i < n; ++i) {
		position[arr[i]] = i;
	}
	for (int i = 0; i < n; i += 2) {
		if (arr[i] / 2 == arr[i + 1] / 2) continue;
		result++;
		int partner = arr[i] ^ 1;
		arr[position[partner]] = arr[i + 1];//交换位置
		arr[i + 1] = partner;
		position[arr[position[partner]]] = position[partner];//更新position矩阵
		position[partner] = i + 1;
	}
	return result;
}

14. 网络延迟时间

有 N 个网络节点,标记为 1 到 N。

给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。

现在,我们向当前的节点 K 发送了一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。

分析:Bellman-Ford算法。遍历N-1次所有的边,进行松弛操作。

int networkDelayTime(vector<vector<int>>& times, int N, int K) {//bellman-ford
	vector<unsigned int> dist(N, INT32_MAX);
	dist[K - 1] = 0;
	for (int i = 1; i <= N - 1; ++i) {
		for (auto edge : times) {
			int start = edge[0] - 1;
			int end = edge[1] - 1;
			if (dist[end] > dist[start] + edge[2]) {
				dist[end] = dist[start] + edge[2];
			}
		}
	}
	int max = 0;
	for (int i = 0; i < N; ++i) {
		if (dist[i] == INT32_MAX) return -1;
		if (dist[i] > max) max = dist[i];
	}
	return max;
}

15. 冗余连接

在本问题中, 树指的是一个连通且无环的无向图。

输入一个图,该图由一个有着N个节点 (节点值不重复1, 2, …, N) 的树及一条附加的边构成。附加的边的两个顶点包含在1到N中间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组。每一个边的元素是一对[u, v] ,满足 u < v,表示连接顶点u 和v的无向图的边。

返回一条可以删去的边,使得结果图是一个有着N个节点的树。如果有多个答案,则返回二维数组中最后出现的边。答案边 [u, v] 应满足相同的格式 u < v。

输入: [[1,2], [1,3], [2,3]]
输出: [2,3]
解释: 给定的无向图为:
 1
 / \
2 - 3

分析:union-find算法,若发现已经属于同一类了。则该边为冗余边。

int find2(int x, vector<int> &father) {
	int f = x;
	while (f!=father[f])
	{
		f = father[f];
	}
	while (x!=father[x]){
		int tem = father[x];
		father[x] = f;
		x = tem;
	}
	return f;
}

bool connect2(int start, int end, vector<int> &father) {
	int fstart = find2(start, father);
	int fend = find2(end, father);
	if (fstart == fend) {
		return false;
	}
	father[fstart] = fend;
	return true;
}

vector<int> findRedundantConnection(vector<vector<int>>& edges) {//冗余连接
	int n = 0;
	for (auto i : edges) {
		for (auto ii : i) {
			if (ii > n) {
				n = ii;
			}
		}
	}
	vector<int> father(n, 0);
	for (int i = 0; i < n; ++i) {
		father[i] = i;
	}
	vector<int> result(2, -1);
	for (auto edge : edges) {
		int i = edge[0] -1;
		int j = edge[1] -1;
		bool newEdge = connect2(i, j, father);
		if (newEdge == false) {
			result[0] = (i > j ? j : i) + 1;
			result[1] = (i<j ? j : i) + 1;
		}
	}
	return result;
}

16. 除法求值(Floyd算法应用)

给出方程式 A / B = k, 其中 A 和 B 均为代表字符串的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。

示例 :
给定 a / b = 2.0, b / c = 3.0
问题: a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
返回 [6.0, 0.5, -1.0, 1.0, -1.0 ]

输入为: vector<pair<string, string>> equations, vector& values, vector<pair<string, string>> queries(方程式,方程式结果,问题方程式), 其中 equations.size() == values.size(),即方程式的长度与方程式结果长度相等(程式与结果一一对应),并且结果值均为正数。以上为方程式的描述。 返回vector类型。

基于上述例子,输入如下:

equations(方程式) = [ [“a”, “b”], [“b”, “c”] ],
values(方程式结果) = [2.0, 3.0],
queries(问题方程式) = [ [“a”, “c”], [“b”, “a”], [“a”, “e”], [“a”, “a”], [“x”, “x”] ].

分析:
1.使用map将所有的字符string映射为0,…,n-1。然后使用floyd算法,计算每两个节点之间是否联通。
2.遍历n次dst矩阵,每次用第i个节点进行松弛操作。dst[x][y]可以由(dst[x][k],dst[k][x])与(dst[k][y],dst[y][k])可达的情况下两两组合而成。

注意:
1.已知i到i设为1
2.未知string也放入dst
3.注意四个数两两组合

vector<double> calcEquation(vector<pair<string, string>> equations, vector<double>& values, vector<pair<string, string>> queries) {//除法求值 floyd
	map<string, int> keys;
	vector<double> result;
	int key = 0;
	for (auto couple : equations) {
		if (keys.find(couple.first) == keys.end()) {
			keys[couple.first] = key;
			++key;
		}
		if (keys.find(couple.second) == keys.end()) {
			keys[couple.second] = key;
			++key;
		}
	}

	for (auto couple : queries) {
		if (keys.find(couple.first) == keys.end()) {
			keys[couple.first] = key;
			++key;
		}
		if (keys.find(couple.second) == keys.end()) {
			keys[couple.second] = key;
			++key;
		}
	}


	vector<vector<double>> matrix(key, vector<double>(key,-1));
	for (int i = 0; i < equations.size(); ++i) {
		int x = keys[equations[i].first];
		int y = keys[equations[i].second];
		matrix[x][y] = values[i];
		matrix[x][x] = 1;
		matrix[y][y] = 1;
	}
	vector<vector<double>> dst = matrix;
	for (int k = 0; k < key; ++k) {
		for (int i = 0; i < key; ++i) {
			for (int j = 0; j < key; ++j) {
				if (dst[i][j] == -1 &&( dst[i][k] != -1 || dst[k][i] != -1)&& (dst[k][j] != -1||dst[j][k] != -1)) {
					if (dst[k][j] != -1 && dst[i][k] != -1) {
						dst[i][j] = dst[i][k] * dst[k][j];
					}
					else if (dst[j][k] != -1 && dst[i][k] != -1) {
						dst[i][j] = dst[i][k] / dst[j][k];
					}
					else if (dst[k][j] != -1 && dst[k][i] != -1) {
						dst[i][j] = dst[k][j] / dst[k][i];
					}
					else if (dst[j][k] != -1 && dst[k][i] != -1) {
						dst[i][j] = 1.0 / dst[k][i] / dst[j][k];
					}
				}
			}
		}
	}
	for (int i = 0; i < queries.size(); ++i) {
		int x = keys[queries[i].first];
		int y = keys[queries[i].second];
		if (dst[x][y] != -1) {
			result.push_back(dst[x][y]);
		}
		else if (dst[y][x] != -1) {
			result.push_back(1.0 / dst[y][x]);
		}
		else{
			result.push_back(-1);
		}
	}
	return result;
}

17. 重新安排行程

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 出发。

说明:

  1. 如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前
  2. 所有的机场都用三个大写字母表示(机场代码)。
  3. 假定所有机票至少存在一种合理的行程。

输入: [[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
输出: [“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]

输入: [[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
输出: [“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
解释: 另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠后。

分析:深度优先遍历,将所有的地点存储成邻接矩阵的形式,然后目标节点用multiset保存,可以自然排序。然后从字符排序最小的点开始递归下去,每次递归完后保存当前递归的节点,然后将结果倒叙。

void findItineraryHelp(string cur, map<string, multiset<string>> &edges, vector<string> &ans) {
	while (!edges[cur].empty()) {
		string next = *edges[cur].begin();
		edges[cur].erase(edges[cur].begin());
		findItineraryHelp(next, edges, ans);
	}
	ans.push_back(cur);
}
vector<string> findItinerary(vector<pair<string, string>> tickets) {//重新安排行程
	map<string, multiset<string>> edges;
	for (auto path : tickets) {
		edges[path.first].insert(path.second);
	}
	vector<string> ans;
	findItineraryHelp("JFK", edges, ans);
	return vector<string>(ans.rbegin(), ans.rend());
}

18. 最小高度树

对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,在所有可能的树中,具有最小高度的树被称为最小高度树。给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。

格式

该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。

你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,因此不会同时出现在 edges 里。

输入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]
  0
  |
  1
 / \
 2  3
输出: [1]

分析:
建立邻接矩阵,一层一层剥离。首先建立一个队列,第一次将所有叶子节点加入队列,然后将叶子节点从其父节点中剥离,若剥离后其父节点变为叶子节点,则将父节点加入到下一次的剥离队列中。可以每次剥离时记录队列的长度,来防止剥离新的叶子节点。最终结果为1个或2个节点

vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {//最小高度树
	if (n == 1) return vector<int>(1, 0);
	vector<unordered_set<int>> matrix(n, unordered_set<int>());
	for (auto edge : edges) {
		matrix[edge.first].insert(edge.second);
		matrix[edge.second].insert(edge.first);
	}
	queue<int> qe;
	for (int i = 0; i < n; ++i) {
		if (matrix[i].size() == 1) {
			qe.push(i);
		}
	}
	while (n > 2) {
		int len = qe.size();
		n = n - len;
		for (int i = 0; i < len; ++i) {
			int tem = qe.front();
			qe.pop();
			matrix[*matrix[tem].begin()].erase(tem);
			if (matrix[*matrix[tem].begin()].size() == 1) {
				qe.push(*matrix[tem].begin());
			}
		}
	}
	vector<int> result;
	while (!qe.empty()) {
		result.push_back(qe.front());
		qe.pop();
	}
	return result;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值