1. 程式人生 > >演算法題2

演算法題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

2. 矩形覆蓋

我們可以用2*1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2*1的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

分析

f ( n ) = f ( n 1 ) + f ( n 2 ) f(n) = f(n-1) + f(n-2)
如果豎著放,則問題縮減為對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}})

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