算法题2

1. 变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

分析:

f ( n ) = f ( 1 ) + f ( 2 ) + . . . + f ( n − 1 ) + 1 f(n)=f(1)+f(2)+...+f(n-1)+1 f(n)=f(1)+f(2)+...+f(n1)+1

2. 矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

分析

f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)
如果竖着放,则问题缩减为对n-1块的填充;如果横着放,缩减为n-2块的填充。

3. 给一个数a,计算平方根

分析:

牛顿法
x t = 1 2 ( x t − 1 + a x t − 1 ) x_t = \frac{1}{2}(x_{t-1}+\frac{a}{x_{t-1}}) xt=21(xt1+xt1a)

4. 树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

分析:
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root1 == null || root2 == null) 
            return false;
        //要么当前结点已经是子树 要么当前结点的左孩子或右孩子存在子树
        return IsSubtree(root1,root2) || HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
    }
    
    public boolean IsSubtree(TreeNode root1,TreeNode root2){
        if(root2 == null)
            return true;
        if(root1 == null)
            return false;
        if(root1.val == root2.val) 
            return IsSubtree(root1.left,root2.left) && IsSubtree(root1.right,root2.right);
        else
            return false;
    }
}

5. 之字形打印树

分析:

构建两个栈,st2先压左节点后压右节点,st1先压右节点后压左节点。

6. 二叉搜索树与双向链表

分析:

指针的引用:
当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递。
如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来的值。

void restructList(TreeNode* root, TreeNode *(&preNode)) {
	if (!root) {
		return;
	}
	restructList(root->left, preNode);
	if (preNode) {
		root->left = preNode;
		preNode->right = root;
	}
	else {
		root->left = NULL;
	}
	preNode = root;
	restructList(root->right, preNode);
}

TreeNode* Convert(TreeNode* pRootOfTree){
	if (!pRootOfTree)
	{
		return pRootOfTree;
	}
	TreeNode *pre = NULL;
	restructList(pRootOfTree,pre);
	TreeNode *cur = pRootOfTree;
	while (cur->left){
		cur = cur->left;
	}
	return cur;
}

后面pre的改变要返回去给前一次的调用

7. 最小的k个数

分析:
  1. 快排的方法:

寻找到第k个位置正确的数,前面的数未最小的k个数。
若index>k-1,则向前找。
若index<k-1,则向后找。

  1. 堆排序方法:

建立一个k元素的大根堆,如果当前值比堆顶元素大则跳过,如果当前值比堆顶元素小则替换掉堆顶元素。

8. 数组中的逆序对

分析:

先把数组分隔成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序,由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。,其实这个排序过程就是归并排序的思路。

9. 丑数

把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

分析:

第一个数为1,然后在1的基础上分别乘2,3,5选最小的数。例如2为所得最小的数,则下一次比较的最小数时2的乘子为2,但是3,5的乘子依旧为1。

int GetUglyNumber_Solution(int index) {  //返回第index个丑数,由2,3,5相乘组成的数
    if (index < 7) {
        return index;
    }
    vector<int> ans(index,0);
    ans[0] = 1;
    int index2 = 0;
    int index3 = 0;
    int index5 = 0;
    for (int i = 1; i < index; ++i) {
        ans[i] = std::min(ans[index2] * 2, std::min(ans[index3] * 3, ans[index5] * 5));
        if (ans[i] == ans[index2] * 2)
            ++index2;
        if (ans[i] == ans[index3] * 3)
            ++index3;
        if (ans[i] == ans[index5] * 5)
            ++index5;
    }
    return ans[index - 1];
}

10. 和为S的连续正数序列

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。

分析:

令n为序列内元素的个数。
如果n为奇数,S%n=0,则可以划分。
如果n为偶数,(S%n)*2==n,则可以划分。
n的上限:(1+n)∗n/2=S,则可得$ n \le \sqrt{2S}$。

11. 约瑟夫换问题(圆圈中最后剩下的数)

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

分析:

原始 0 1 2 3 4 5 6 7 8 9

旧环 0 1 2 ~ 4 5 6 7 8 9

新环 6 7 8 ~ 0 1 2 3 4 5

旧环到新环的映射: (旧环中编号-最大报数值)%旧总人数

新环到旧环的映射: ( 新环中的数字 + 最大报数值 )% 旧总人数

12. 累加1+2+…

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

分析:
利用A&&B的性质,前部分A不成立则不执行后部分B。

    int Sum_Solution(int n) {
        int result = n;
        n && (result+=Sum_Solution(n-1));
        return result;
    }

13. 不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

分析:

a.两个数相加,如果忽略掉进位相当于按位异或,010111 = 101。
b.进位项相当于两个数按位与&并左移一位<<1,(010&111)<<1=0100。
c.如果进位项不等于0,则重复a.b过程,如果进位项为0,则结果为a步骤的结果。

int Add(int num1, int num2){//计算两个数相加和,不能用加减乘除运算
	int result = num1 ^ num2;
	int carry = (num1&num2) << 1;
	while (carry){
		int tem = result;
		result = carry ^ result;
		carry = (carry&tem) << 1;
	}
	return result;
}

14. 数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

分析:

1.把当前序列当成是一个下标和下标对应值是相同的数组;
2.遍历数组,判断当前位的值和下标是否相等: 2.1. 若相等,则遍历下一位; 2.2. 若不等,则将当前位置i上的元素和a[i]位置上的元素比较:若它们相等,则成功!若不等,则将它们两交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标并不一定对应;重复2.2的操作,直到当前位置i的值也为i,将i向后移一位,再重复2.

bool duplicate(int numbers[], int length, int* duplication) {
	for (int i = 0; i < length; ++i) {
		while (numbers[i] != i)
		{
			if (numbers[i] == numbers[numbers[i]]) {
				*duplication = numbers[i];
				return true;
			}
			else {
				swap(numbers[i], numbers[numbers[i]]);
			}
		} 
	}
	return false;
}

15. 匹配正则表达式

请实现一个函数用来匹配包括’.‘和’‘的正则表达式。模式中的字符’.‘表示任意一个字符,而’'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配。

bool matchCore(char *str, char *pattern) {
	if (*str == '\0' && *pattern == '\0') {
		return true;
	}
	if (*str != '\0' && *pattern == '\0') {
		return false;
	}
	if (*str == '\0'&&*pattern != '\0'&&*(pattern + 1) != '*') {
		return false;
	}
	if (*(pattern + 1) == '*') {
		if (*pattern == *str || (*pattern == '.'&&*str != '\0')) {
			return matchCore(str, pattern + 2) || matchCore(str + 1, pattern);
		}
		else {
			return matchCore(str, pattern + 2);
		}
	}
	if (*pattern == *str || (*pattern == '.'&&*str != '\0')) {
		return matchCore(str + 1, pattern + 1);
	}
	return false;
}

16. 镜像二叉树

判断一颗二叉树是不是镜像的。

bool assistSymmetrical(TreeNode* left, TreeNode* right) {
	if (left == NULL) {
		return right == NULL;
	}
	if (right == NULL) {
		return false;
	}
	if (left->val != right->val) {
		return false;
	}
	return assistSymmetrical(left->right, right->left) && assistSymmetrical(left->left, right->right);
}

bool isSymmetrical(TreeNode* pRoot)//判断是否为镜像二叉树
{
	if (pRoot == NULL) {
		return true;
	}
	return assistSymmetrical(pRoot->left, pRoot->right);
}

17. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

分析:

1.建立一个大根堆,一个小根堆。

2.当大根堆中元素数为小根堆元素+2时,将大根堆的堆顶取出,放入小根堆。

3.循环,如果大根堆堆顶大于小跟堆堆顶,则互换堆顶,直到大根堆堆顶小于等于小根堆堆顶。

4.若元素数为奇数,返回大根堆堆顶,如果为偶数,返回大根堆堆顶+小跟堆堆顶的平均值。

注意:建堆代码以及删除元素代码。

class Solution {//数据流的中位数
public:
	void Insert(int num){
		dataStream.push_back(num);
	}

	double GetMedian(){
		std::multiset<int, std::greater<int>> bigH;
		std::multiset<int, std::less<int>> littleH;
		int num = dataStream.size();
		for (int i = 0; i < num; ++i) {
			bigH.insert(dataStream[i]);
			if (bigH.size() == littleH.size() + 2) {
				int tem = *bigH.begin();
				littleH.insert(tem);
				bigH.erase(bigH.begin());
			}
			while (!bigH.empty()&&!littleH.empty()&&*bigH.begin()>*littleH.begin()){
				int big = *bigH.begin();
				bigH.erase(bigH.begin());
				int little = *littleH.begin();
				littleH.erase(littleH.begin());
				littleH.insert(big);
				bigH.insert(little);
			}
		}

		if (num % 2 == 1) {
			return *bigH.begin();
		}
		else {
			return (*bigH.begin() + *littleH.begin()) / 2.0;
		}
	}
private:
	vector<int> dataStream;
};

18. 之字形打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

分析:

设计两个栈,st1先压右节点再压左节点,st2先压左节点再压右节点。

vector<vector<int> > Print(TreeNode* pRoot) {//之字形打印树
	vector<vector<int>> result;
	if (pRoot == NULL) {
		return result;
	}
	stack<TreeNode *> st1;
	stack<TreeNode *> st2;
	st1.push(pRoot);
	while (!st1.empty() || !st2.empty()) {
		vector<int> temVec;
		if (!st1.empty()) {
			while (!st1.empty()){
				TreeNode *cur = st1.top();
				st1.pop();
				temVec.push_back(cur->val);
				if (cur->left) {
					st2.push(cur->left);
				}
				if (cur->right) {
					st2.push(cur->right);
				}
			}
		}
		else {
			while (!st2.empty()) {
				TreeNode *cur = st2.top();
				st2.pop();
				temVec.push_back(cur->val);
				if (cur->right) {
					st1.push(cur->right);
				}
				if (cur->left) {
					st1.push(cur->left);
				}
			}
		}
		result.push_back(temVec);
	}
	return result;
}

19. 把二叉树打印成多行

分析:

设计两个指针,一个保存当前最新压入队列的节点,一个保存当前打印行最后一个节点。

vector<vector<int> > Print(TreeNode* pRoot) {//按行打印二叉树
	vector<vector<int>> result;
	if (pRoot == NULL) {
		return result;
	}
	queue<TreeNode*> helpQe;
	helpQe.push(pRoot);
	TreeNode *rowLast = pRoot;
	TreeNode *temLast = pRoot;
	vector<int> rowVec;
	while (!helpQe.empty()){
		TreeNode *tem = helpQe.front();
		helpQe.pop();
		rowVec.push_back(tem->val);
		if (tem->left != NULL) {
			helpQe.push(tem->left);
			temLast = tem->left;
		}
		if (tem->right != NULL) {
			helpQe.push(tem->right);
			temLast = tem->right;
		}
		if (rowLast == tem) {
			rowLast = temLast;
			result.push_back(rowVec);
			rowVec.clear();
		}
	}
	return result;
}

20. 序列化二叉树

分析:

注意序列化若遇到NULL返回#,反序列化时,遇到#与值用完,都返回NULL。

void serializeHelp(TreeNode *root, string &str) {
	if (root == NULL) {
		str += "#!";
		return;
	}
	str += (std::to_string(root->val) + "!");
	serializeHelp(root->left, str);
	serializeHelp(root->right, str);
}
char* Serialize(TreeNode *root) {	
	if (root == NULL) {
		char *ans = new char;
		*ans = '\0';
		return ans;
	}
	string str;
	serializeHelp(root, str);
	char *result = new char[str.size() + 1];
	for (int i = 0; i < str.size(); ++i) {
		result[i] = str[i];
	}
	result[str.size()] = '\0';
	return result;
}

TreeNode* deserializeHelp(queue<string> &nodeValue) {
	if (nodeValue.empty()) {
		return NULL;
	}
	string tem = nodeValue.front();
	nodeValue.pop();
	if (tem == "#") {
		return NULL;
	}
	else {
		TreeNode *newNode = new TreeNode(atoi(tem.c_str()));
		newNode->left = deserializeHelp(nodeValue);
		newNode->right = deserializeHelp(nodeValue);
		return newNode;
	}
}

TreeNode* Deserialize(char *str) {
	if (*str == '\0') {
		return NULL;
	}
	queue<string> nodeValue;
	string tem;
	while (*str!='\0'){
		if (*str != '!') {
			tem += *str;
			++str;
		}
		else {
			nodeValue.push(tem);
			tem.clear();
			++str;
		}
	}
	tem = nodeValue.front();
	nodeValue.pop();
	TreeNode *head = new TreeNode(atoi(tem.c_str()));
	head->left = deserializeHelp(nodeValue);
	head->right = deserializeHelp(nodeValue);
	return head;
}

21. 二叉搜索树的第k个节点

分析:

设置一个引用值k,用中序遍历的方法,每次遍历到一个节点做k–处理.若k=0则返回当前节点。引用的k值能够在任何一层递归中判断是否达到终止条件。

注意:终止条件及路径返回的完整性。

TreeNode* kthNode(TreeNode* pRoot, int &k) {
	if (pRoot == NULL) {
		return NULL;
	}
	TreeNode *left = kthNode(pRoot->left, k);
	if (k == 1) {
		return left;
	}
	--k;
	if (k == 1) {
		return pRoot;
	}
	TreeNode *right = kthNode(pRoot->right, k);
	if (k == 1) {
		return right;
	}
	return NULL;
}

TreeNode* KthNode(TreeNode* pRoot, int k){//找到搜索二叉树中第k大的数
	if (pRoot == NULL) {
		return NULL;
	}
	int w = k + 1;
	return kthNode(pRoot, w);
}

22. 滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

分析:

时间复杂度可以做到O(N).
首先建立一个双向队列qmax记录数组的下标,若当前数为arr[i],放入规则为

1.如果qmax为空,将i放入qmax尾部。

2.如果qmax不为空,qmax队尾下标为j,若arr[i]大于qmax的队尾,则弹出qmax队尾,直到qmax队尾大于等于arr[i]或qmax为空。

3.判断qmax队首是否满足窗口范围要求,若j<=i-Wsize,则循环弹出qmax队首。

4.qmax队首元素为当前窗口的最大值。

注意:双向队列的各种操作。

vector<int> maxInWindows(const vector<int>& num, unsigned int size){//滑动窗口的最大值
	vector<int> result;
	if (num.size() < size) {
		return result;
	}
	std::deque<int> qMax;
	for (int i = 0; i < num.size(); ++i) {
		while (!qMax.empty() && num[qMax.back()] <= num[i]) {
			qMax.pop_back();
		}
		qMax.push_back(i);
		while (!qMax.empty() &&  (qMax.front() + size <= i)) {
			cout << qMax.front() << "    " << i - size << endl;
			qMax.pop_front();
		}
		if (i >= size - 1) {
			result.push_back(num[qMax.front()]);
		}
	}
	return result;
}

23. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

分析:

递归寻找路径,每次都检查上下左右是否满足。结果相或。设计一个引用的矩阵或向量来记录元素是否被使用过,记得恢复。

bool findPath(vector<int> history, char *matrix, int rows, int cols, char* str, int index, int row, int col) {
	if (str[index] == '\0') {
		return true;
	}
	bool result = false;
	char tem = str[index];
	if (row - 1 >= 0 && matrix[col + (row - 1)*cols] == tem && history[col + (row - 1)*cols] != 1) {
		history[col + (row - 1)*cols] = 1;
		result |= findPath(history, matrix, rows, cols, str, index+1, row-1, col);
		history[col + (row - 1)*cols] = 0;

	}
	if (row + 1 < rows && matrix[col + (row + 1)*cols] == tem && history[col + (row + 1)*cols] != 1) {
		history[col + (row + 1)*cols] = 1;
		result |= findPath(history, matrix, rows, cols, str, index + 1, row + 1, col);
		history[col + (row + 1)*cols] = 0;

	}
	if (col - 1 >= 0 && matrix[col - 1 + row * cols] == tem && history[col - 1 + row * cols] != 1) {
		history[col - 1 + row * cols] = 1;
		result |= findPath(history, matrix, rows, cols, str, index + 1, row, col - 1);
		history[col - 1 + row * cols] = 0;

	}
	if (col + 1 < cols && matrix[col + 1 + row * cols] == tem && history[col + 1 + row * cols] != 1) {
		history[col + 1 + row * cols] = 1;
		result |= findPath(history, matrix, rows, cols, str, index + 1, row, col + 1);
		history[col + 1 + row * cols] = 0;
	}
	return result;
}

bool hasPath(char* matrix, int rows, int cols, char* str){//寻找矩阵中的路径
	if (matrix == NULL) {
		return false;
	}
	vector<int> history(rows*cols, 0);
	bool result = false;
	char tem = str[0];
	for (int i = 0; i < cols; ++i) {
		for (int j = 0; j < rows; ++j) {
			if (matrix[i + j * cols] == tem && history[i + j * cols] != 1) {
				//cout << "col: " << i << "  " << "row: " << j << "    char:" << matrix[i + j * cols] << endl;
				history[i + j * cols] = 1;
				result |= findPath(history, matrix, rows, cols, str, 1, j, i);
				history[i + j * cols] = 0;
			}
		}
	}
	return result;
}

24. 机器人的运动范围

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

分析:

设计一个history矩阵或向量,记录是否走个这个路径。

设计一个递归函数,如果坐标满足阈值要求,则返回分别向上下左右能走的格子数+1,如果不满足阈值要求或矩阵范围要求,返回0,如果路径已经被走过,返回0。

int twoNumberBitSum(int a, int b) {
	int result = 0;
	while (a>0){
		result += a % 10;
		a = a / 10;
	}
	while (b>0){
		result += b % 10;
		b = b / 10;
	}
	return result;
}

int findMovingCount(int threshold, int rows, int cols, vector<int> &history, int row, int col) {
	if (twoNumberBitSum(row, col) > threshold || row < 0 || row >= rows || col < 0 || col >= cols) {
		return 0;
	}
	if (history[col + cols * row] == 1) {
		return 0;
	}
	history[col + cols * row] = 1;
	return findMovingCount(threshold, rows, cols, history, row - 1, col) + findMovingCount(threshold, rows, cols, history, row + 1, col)+
		findMovingCount(threshold, rows, cols, history, row, col - 1) + findMovingCount(threshold, rows, cols, history, row, col + 1) + 1;
}

int movingCount(int threshold, int rows, int cols){//机器人的运动范围
	if (threshold < 0) {
		return 0;
	}
	vector<int> history(rows*cols, 0);
	return findMovingCount(threshold, rows, cols, history, 0, 0);
}

25. 图的连通分量

把一个图的最大连通子图称为它的连通分量,连通分量有如下特点:
1)是子图;
2)子图是连通的;
3)子图含有最大顶点数。
“最大连通子图”指的是无法再扩展了,不能包含更多顶点和边的子图。显然,对于连通图来说,它的最大连通子图就是其本身,连通分量也是其本身。

26. 图的最短路径算法

深度优先搜索
void dfs(int cur, int dest, int curLen, vector<vector<int>> edge, int &minLen, vector<int> &mark, vector<int> &nowPath, vector<int> &resultPath)
{
    if(curLen > minLen) return;
    if(cur == dest){
        if(minLen > curLen){
            resultPath = nowPath;
            minLen = curLen;
        }
        return;
    }
    for(int i = 0; i < n; ++i){
        if(mark[i] == 0 && edge[cur][i] != 0 && edge[cur][i] != inf){
            mark[i] = 1;
            nowPath.push_back(i);
            newLen = curLen + edge[cur][i];
            dfs(i, dest, newLen, edge, minLen, mark, nowPath, resultPath)
            nowPath.pop_back(i);
            mark[i] = 0;
        }
    }
}
Dijkstra 迪杰斯特拉算法(解决单源最短路径),时间复杂度O(N*logN)

基本思想:每次找到离源点(如1号结点)最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。
基本步骤:
1,设置标记数组book[]:将所有的顶点分为两部分,已知最短路径的顶点集合P和未知最短路径的顶点集合Q,很显然最开始集合P只有源点一个顶点。book[i]为1表示在集合P中;
2,设置最短路径数组dst[]并不断更新:初始状态下,令dst[i] = edge[s][i] (s为源点,edge为邻接矩阵),很显然此时dst[s]=0,book[s]=1。此时,在集合Q中可选择一个离源点s最近的顶点u加入到P中。并依据以u为新的中心点,对每一条边进行松弛操作(松弛是指由结点s–>j的途中可以经过点u,并令dst[j]=min{dst[j], dst[u]+edge[u][j]}),并令book[u]=1;
3,在集合Q中再次选择一个离源点s最近的顶点v加入到P中。并依据v为新的中心点,对每一条边进行松弛操作(即dst[j]=min{dst[j], dst[v]+edge[v][j]}),并令book[v]=1;
4,重复3,直至集合Q为空。

void dfs(int n, vector<vector<int>> edge, int k){
    vector<int> book(n, 0);
    vector<int> dst(n, 0);
    book[k] = 1;
    for(int i = 0; i < n; ++i){
        dst[i] = edge[k][i];
    }
    for(int m = 0; m < n-1; ++m){
        int minDst = INT32_MAX;
        int minLabel = -1;
        for(int i = 0; i< n; ++i){
            if(book[i] == 0 && dst[i] < minDst){
                minDst = dst[i];
                minLabel = i;
            }
        }
        book[minLabel] = 1;
        for(int i = 0; i < n; ++i){
            if(book[i] == 0 && dst[i] > dst[minLabel] + edge[minLabel][i]){
                dst[i] = dst[minLabel] + edge[minLabel][i];
            }
        }
    }
}
Floyd弗洛伊德算法(解决多源最短路径):时间复杂度O(n^ 3),空间复杂度O(n^2)

基本思想:最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转…允许经过1~n号所有顶点进行中转,来不断动态更新任意两点之间的最短路程。即求从i号顶点到j号顶点只经过前k号点的最短路程。

分析如下:
1,首先构建邻接矩阵Floyd[n+1][n+1],假如现在只允许经过1号结点,求任意两点间的最短路程,很显然Floyd[i][j] = min{Floyd[i][j], Floyd[i][1]+Floyd[1][j]}.
2,接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短距离,在已经实现了从i号顶点到j号顶点只经过前1号点的最短路程的前提下,现在再插入第2号结点,来看看能不能更新更短路径,故只需在步骤1求得的Floyd[n+1][n+1]基础上,进行Floyd[i][j] = min{Floyd[i][j], Floyd[i][2]+Floyd[2][j]};…
3,很显然,需要n次这样的更新,表示依次插入了1号,2号…n号结点,最后求得的Floyd[n+1][n+1]是从i号顶点到j号顶点只经过前n号点的最短路程。故核心代码如下:

vector<vector<int>> floyd = edge
for(int k = 0; k < n; ++k){
    for(int i = 0; i < n; ++i){
        for(int j = 0; j < n; ++j){
            if(floyd[i][j] < floyd[i][k] + floyd[k][j]){
                floyd[i][j] = floyd[i][k] + floyd[k][j];
            }
        }
    }
}
Bellman-Ford算法

贝尔曼-福特算法,它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于Dijkstra算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。
第一,初始化所有点。每一个点保存一个值,表示从原点到达这个点的距离,将原点的值设为0,其它的点的值设为无穷大(表示不可达)。
第二,进行循环,循环下标为从1到n-1(n等于图中点的个数)。在循环内部,遍历所有的边,进行松弛计算。
第三,遍历途中所有的边(edge(u,v)),判断是否存在这样情况:
d(v)>d(u) + w(u,v)
则返回false,表示途中存在从源点可达的权为负的回路。
之所以需要第三部分的原因,是因为,如果存在从源点可达的权为负的回路。则应为无法收敛而导致不能求出最短路径。

可知,Bellman-Ford算法寻找单源最短路径的时间复杂度为O(VE).

class Edge{
    int src, dest, weight;    
};
class Graph{
    int v,e;
    Edge *edge;
};
void BF(int k, Graph *graph){
    int v = graph->v;
    int e = graph->e;
    vector<int> dst(v, INT32_MAX);
    dst[k] = 0;
    for(int k = 1; k <= v-1; ++k){
        for(int i = 0; i < e; ++i){
            int st = graph->edge[i].src;
            int end = graph->edge[i].dest;
            if(v[end] > v[st] + graph->edge[i].weight){
                v[end] = v[st] + graph->edge[i].weight;
            }
        }
    }
    for(int i = 0; i < e; ++i){
        int st = graph->edge[i].src;
        int end = graph->edge[i].dest;
        if(v[end] > v[st] + graph->edge[i].weight){
            return false;
        }
    }
    return true;
}

27. 最短路径问题

给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s,终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。
costMatrix[n][n], lenMatrix[n][n]

分析:

设计两个向量,一个存储最短距离dst,一个存储最少花费spend。

void dfs(int n, int k, vector<vector<int>> edge, vector<vector<int>> cost){
    vector<int> spend(n, 0);
    vector<int> dst(n, 0);
    vector<int> book(n, 0);
    book[k] = 1;
    for(int i = 0;i < n; ++i){
        spend[i] = cost[k][i];
        dst[i] = edge[k][i];
    }
    for(int m = 0; m < n-1; ++m){
        int minLen = INT32_MAX;
        int minSpend = INT32_MAX;
        int minLabel = -1;
        for(int i = 0; i < n; ++i){
            if(book[i] == 0 && (minLen > dst[i] || (minLen == dst[i] && minSpend > cost[i]))){
                minLabel = i;
                minLen = dst[i];
                minSpend = cost[i];
            }
        }
        book[minLabel] = 1;
        for(int i = 0; i < n; ++i){
            if(book[i] == 0 && (dst[i] > dst[minLabel] + edge[minLabel][i] ||
            (dst[i] == dst[minLabel] + edge[minLabel][i] && spend[i] >spend[minLabel] + cost[minLabel][i]))){
                dst[i] = dst[minLabel] + edge[minLabel][i];
                spend[i] = spend[minLabel] + cost[minLabel][i];
            }
        }
    }
}

28. 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

分析:先判断链表的长度len,k=k%len,设置一个快指针,先走k步,再设置一个慢指针更快指针同时向后推,直到快指针为最后一个元素。此时慢指针的下一个元素为新的头指针。

ListNode* rotateRight(ListNode* head, int k) {
	if (!head) return head;
	ListNode *fast = head;
    int num = 0;
    while (fast)
	{
		fast = fast->next;
		num++;
	}
	int m = k % num;
    fast= head;
	for (int i = 1; i <= m; i++) {
		fast = fast->next;
		if (fast == NULL) {
			fast = head;
		}
	}
	ListNode *slow = head;
	while (fast->next != NULL) {
		fast = fast->next;
		slow = slow->next;
	}
	ListNode *newHead = slow->next;
	if (newHead == NULL) {
		newHead = head;
	}
	fast->next = head;
	slow->next = NULL;
	return newHead;
}

29. 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

分析:使用两个变量记录首行或者首列是否含有0,然后用首行或者首列来记录该行或列是否需要置0.

30. 搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
分析:先用二分搜索列,再用二分搜索行。

30. 等差数列划分

函数要返回数组 A 中所有为等差数组的子数组个数。

A = [1, 2, 3, 4]
返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。

分析:动态规划。保存当前为结尾所能组成的等差数列的个数。例如上为help=[0,0,1,2]。然后将help矩阵相加。

int numberOfArithmeticSlices(vector<int>& A) {//等差数列划分
	int n = A.size();
	if (n < 3) return 0;
	vector<int> help(n, 0);
	int key = A[1] - A[0];
	int ans = 0;
	for (int i = 2; i < n; ++i) {
		if (A[i] - A[i - 1] == key) {
			help[i] = help[i - 1] + 1;
		}
		else {
			key = A[i] - A[i - 1];
		}
		ans += help[i];
	}
	return ans;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值