回溯

記錄整理回溯算法專題

1. 八皇后

問題描述:
在一個8*8的矩形格子中排放8個皇后,要滿足的條件包括: 任意兩個皇后都不能在同一行/列/對角線(斜率等於1/-1). 要求編程給出所有第一行第一列有皇后的解 (注:解的輸出用8個數字表示,如:基本解{1,5,8,6,3,7,2,4},其中’1’表示第一行第一列即(1,1)處有皇后, 後同)

思路:
跟窮舉查不多,我們依次在每一行放一個皇后,而一行有八列,所以我們在放置一個皇后時,就要馬上判斷皇后放置的列是否符合條件,如果符合,我們就放置下一個皇后,如果不符合,我們就嘗試着將皇后放置在下一列,接着像剛纔一樣判斷皇后放置的列是否合法…如此循環,直到最後一列爲止。

#include <iostream>
#include <stdio.h>
#include <math.h>
#define N 8
int queen[N] = {0}, sum = 0; // 數組queen[N]的下標表示皇后所在的行,queen[N]其值表示皇后所在的列

/**
 * 打印八皇后圖表形式 
 */ 
void Paint() {	                    
	for (int i = 0; i < N; i++) {
		for (int j=0; j < N; j++) {
			if(queen[i] == j)
				printf("■ ");       // 輸出皇后 
			else
				printf("□ ");
		}
		printf("\n");
	}
	printf("\n");
} 

/**
 * 輸出所有皇后座標
 */ 
void PrintfPutQueen() {	            
	for (int i = 0; i < N; i++) {
		printf("(%d,%d) ", i, queen[i]);
	} 
	printf("\n");
}

int CanPlace(int row) {
	for (int j = 0; j < row; j++) {	// 將第 row 行皇后與前面所有行的皇后進行比較
        // 檢查橫排和對角線上是否可以放置皇后 
		if(queen[row] == queen[j] || abs(queen[j]-queen[row]) == (row-j) )    
			return 0;	            // 不能放置皇后 
	}
	return 1;	                    // 可以放置皇后 
} 

/**
 * 放置皇后
 */  
void PutQueen(int row) {
	if (row >= N) {	//皇后已經全部放完時 
		Paint();
		sum++;
//		return;
	} else {
		for (int i = 0;i < N; i++) { // 總共有N列,一列一列的試探放置皇后 
			queen[row] = i;          // 將第row行皇后放在第 i 列上面
			if (CanPlace(row)) {	 // 判斷是否可以放置該皇后 
				PutQueen(row + 1);   // 放下一個行皇后 
			} 
		}
	}
}

int main() {
	PutQueen(0);					 // 從橫座標爲0開始依次嘗試
	printf("\nSum = %d\n", sum);
	return 0;
}

不使用全局變量方式:

#include <stdio.h>
#include <stdlib.h>
#define N 8

/**
 * 打印八皇后圖表形式 
 */ 
void Paint(int *queen) {
	for (int i = 0; i < N; i++) {
		for (int j=0; j < N; j++) {
			if(queen[i] == j)
				printf("■ ");       // 輸出皇后 
			else
				printf("□ ");
		}
		printf("\n");
	}
	printf("\n");
} 

/**
 * 輸出所有皇后座標
 */ 
void PrintfPutQueen(int *queen) {	            
	for (int i = 0; i < N; i++) {
		printf("(%d,%d) ", i, queen[i]);
	} 
	printf("\n");
}

int CanPut(int row, int *queen) {
	for (int i = 0; i < row; i++) { // 將第 row 行皇后與前面所有行的皇后進行比較
		if (queen[row] == queen[i] || (abs(queen[row] - queen[i]) == (row - i)))    // 檢查橫排和對角線上是否可以放置皇后 
			return 0;               // 不能放置皇后 
	}
	return 1;                       // 可以放置皇后 
}

/**
 * 放置皇后
 */  
int PutQueen(int row, int *queen) {
	int sum = 0;
	if (row >= N) {				    // 說明皇后都已經放好了
        Paint(queen);
		return 1;
	} else {
		for (int i = 0; i < N; i++) {               // 總共有N列,一列一列的試探放置皇后 
			queen[row] = i;		                    // 將第row行皇后放置在第i列
			if (CanPut(row, queen)) {               // 判斷是否可以放置該皇后 
				sum += PutQueen(row + 1, queen);    // 放下一個行皇后 
			}
		}
	}
	return sum;
}

int main() {
    // 數組queen[N]的下標表示皇后所在的行,queen[N]其值表示皇后所在的列
	int queen[N] = {0}, sum = 0; 	
	sum = PutQueen(0, queen);
	printf("Sum = %d\n", sum);
	return 0;
}

2. 2n皇后

問題描述:
給定一個n*n的棋盤,棋盤中有一些位置不能放皇后。
現在要向棋盤中放入n個黑皇后和n個白皇后,使任意的兩個黑皇后都不在同一行、同一列或同一條對角線上,任意的兩個白皇后都不在同一行、同一列或同一條對角線上。問總共有多少种放法?n小於等於8。

思路:
其實就是在n皇后基礎上改進的,按照n皇后思想,先放黑的,黑的都放好後再放白的,就是要多加一個
判斷已經放了黑的的位置就不能再放白的了。

輸入格式
  輸入的第一行爲一個整數n,表示棋盤的大小。
  接下來n行,每行n個0或1的整數
(如果一個整數爲1,表示對應的位置可以放皇后,如果一個整數爲0,表示對應的位置不可以放皇后)

輸出格式
  輸出一個整數,表示總共有多少种放法。

樣例輸入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
樣例輸出
2

樣例輸入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
樣例輸出
0

/**
 * Creation         :       2019.04.04 14:50
 * Last Reversion   :       2019.03.04 15:22
 * Author           :       Lingyong Smile {[email protected]}
 * File Type        :       cpp
 * -----------------------------------------------------------------
 * EightQueen(2n皇后問題)

 * -----------------------------------------------------------------
 * Crop right @ 2019 Lingyong Smile {[email protected]}
 */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int white[10], black[10], a[10][10], sum = 0, N = 0;

int CanPlaceBlack(int row) {
    for (int i = 0; i < row; i++) {
        if (black[row] == black[i] || (abs(black[row] - black[i]) == (row - i)))
            return 0;
    }
    return 1;
}

int CanPlaceWhite(int row) {
    for (int i = 0; i < row; i++) {
        if (white[row] == white[i] || (abs(white[row] - white[i]) == (row - i))) 
            return 0;
    }
    return 1;
}

void PutWhite(int row) {
    if (row >= N) {
        sum++;
    } else {
        for (int i = 0; i < N; i++) {
            white[row] = i;
            if (a[row][i] == 0 || black[row] == white[row]) // 該位置不能放或者已經放了黑的
                continue;
            if (CanPlaceWhite(row)) {
                PutWhite(row + 1);
            }
        }
    }
}

void PutBlack(int row) {
    if (row >= N) {
        PutWhite(0);
    } else {
        for (int i = 0; i < N; i++) {
            black[row] = i;
            if (a[row][i] == 0)
                continue;
            if (CanPlaceBlack(row)) {
                PutBlack(row + 1);
            }
        }
    }
}

int main() {
    scanf("%d", &N);
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            scanf("%d", &a[i][j]);
        }
    }
    PutBlack(0);
    printf("Sum = %d\n", sum);    
    return 0;
}

3. 國際象棋

題目描述:
​ 假設國際象棋盤有5*5共有25個格子。設計一個程序,使棋子從初試位置(棋盤格編號爲1的位置)開始跳馬,能夠把棋盤的格子全部走一遍,每個格子只允許走一次,要求:
​ (1)輸出一個解(用二維數組來記錄馬跳的過程,即【步號,棋盤格編號】,左上角爲第1步起點)
​ (2)求總共有多少解?
棋盤格編號爲:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25

思想:
​ 回溯思想 (和八皇后一個思路,都是要走完整個棋盤,即遞歸出口條件是類似的)

#include <stdio.h>
int a[5][5];                                // 棋盤
int b[5][5] = {0};                          // 用於標記棋盤對應位置是否已走過
int step[25] = {0}, step_num = 0, sum = 0;  // 用於記錄每步走過的位置,sum爲總共有多少總跳法
                                           7
void Init(int a[][5]) {                     // 初始化棋盤
    int k = 1;
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            a[i][j] = k++;
        }
    }
}

/**
 * 判斷該跳是否可行
 */
int Check(int x, int y) {
    if (x < 0 || x >= 5 || y < 0 || y >= 5 || b[x][y] == 1)  // 不要忘了判斷該位置是否已走過
        return 0;
    else
        return 1;
}

/**
 * 探尋可走的路
 */
void Search(int x, int y) {
    if (Check(x, y)) {               // 如果該跳可行
        step[step_num++] = a[x][y];  // step下標從1開始
        if (step_num == 25) {        // 若25個均跳好了
            sum++;
            printf("\nThe %d Jump:\n", sum);
            for (int i = 0; i < step_num; i++) {
                printf("[%d,%d]\n", i + 1, step[i]);
            }
        }
        b[x][y] = 1;                // 標記該位置已經走過了
        Search(x + 1, y + 2);       // 遞歸調用,尋找下一跳
        Search(x + 1, y - 2);
        Search(x - 1, y + 2);
        Search(x - 1, y - 2);
        Search(x + 2, y + 1);
        Search(x + 2, y - 1);
        Search(x - 2, y + 1);
        Search(x - 2, y - 1);
        step_num--;                 // 若尋找的下一跳都不可行,取消這一步,回退到上一步
        b[x][y] = 0;                // 把這步設置標記未走過
    }
}

int main() {
    Init(a);                        // 初始化棋盤
    Search(0, 0);
    printf("\nSum = %d", sum);
    return 0;
}

4. 馬跳日

題目描述:
​ 在半個中國象棋棋盤上,馬在左下角(1, 1)處, 馬走日字,而且只能往右走,
不能向左,可上可下,求從起點到(m, n)處有幾種不同的走法(函數的遞歸調用)
其中,1<= m <=5, 1<= n <=9

#include <stdio.h>

int horse(int x1, int y1, int x2, int y2) {
    int sum = 0;
    if (y1 > y2 || (y1 == y2 && x1 != x2))  // 起點和終點不能在同一列
        return 0;
    else if (x1 == x2 && y1 == y2)  		// 走到了終點,返回1,然後遞歸回去
        return 1;
    else {
        if (x1 - 1 >= 1 && y1 + 2 <= 9) sum += horse(x1 - 1, y1 + 2, x2, y2);
        if (x1 - 2 >= 1 && y1 + 1 <= 9) sum += horse(x1 - 2, y1 + 1, x2, y2);
        if (x1 + 2 <= 5 && y1 + 1 <= 9) sum += horse(x1 + 2, y1 + 1, x2, y2);
        if (x1 + 1 <= 5 && y1 + 2 <= 9) sum += horse(x1 + 1, y1 + 2, x2, y2);
        return sum;
    }
}

int main() {
    int m, n;
    printf("請輸入目的地址:\n");
    scanf("%d %d", &m, &n);
    printf("目的結果數爲%d\n", horse(1, 1, m, n));
    return 0;
}

5. 騎士最短路徑

問題描述:
​ 在一個8*8的矩形格子中找起始位置 start(x, y) 到 終點位置 end(x, y) 的最短路徑。
輸出最短的路徑長度,並輸出路徑順序點(這部分沒有完成)
思路:
​ 使用BFS思想(類似於層次遍歷),單源最短路徑。

#include <iostream>
using namespace std;
#include <queue>

typedef struct Point {
    int x;
    int y;
} Point;

// 定義馬日跳
int direction[][2] = {{2, 1}, {2, -1}, {-2, 1}, {-2, -1},
                      {1, 2}, {1, -2}, {-1, 2}, {-1, -2}};

int BFS(Point start, Point end) {
    int step[8][8] = {0};                       // 存放起始點到達8x8網格每一點時刻走的步數
    Point cur, next;
    queue<Point> que;
    que.push(start);                            // 起始點入隊
    while (!que.empty()) {                      // 當隊列不爲空
        cur = que.front();                      // 隊首元素出隊
        que.pop();
        if (cur.x == end.x && cur.y == end.y) {// 跳到終點時,返回當前走的路徑長度,即爲最短路徑
            return step[cur.x][cur.y];
        }
        for (int i = 0; i < 8; i++) {           // 沒跳到終點,繼續遍歷當前位置的下一跳
            next.x = cur.x + direction[i][0];
            next.y = cur.y + direction[i][1];
            if (next.x < 1 || next.x > 8 || next.y < 1 || next.y > 8) continue;
            if (step[next.x][next.y] == 0) {    // 若當前點未被訪問,則將其加入隊列
                step[next.x][next.y] = step[cur.x][cur.y] + 1;
                que.push(next);
            }
        }
    }
}

int main() {
    Point start, end;
    cin >> start.x >> start.y;
    cin >> end.x >> end.y;
    cout << "The shortest path length is " << BFS(start, end) << endl;
}

6. 矩陣中的路徑

題目描述
​ 請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符
的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,
向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能
再次進入這個格子。 例如上圖:abtgcfcsjdeh 這樣的3 X 4 矩陣中包
含一條字符串"bfce"的路徑,但是矩陣中不包含"abfb"路徑,因爲字符串的第一
個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子。
輸入:
3
4
abtgcfcsjdeh
bfce
abfb
輸出:
1
0

#include <iostream>
#include <cstring>  
#include <string>  
using namespace std;

#define MAX_SIZE 1000
/**
 * 思想:
 *    當矩陣座標中爲(row, col)的格子和路徑字符串中下標爲pathLength的字符串一樣時,並且
 * 該格子未被訪問時,則從4個相鄰的格子(row, col-1)、(row, col+1)、(row-1, col)、(row+1, col)
 * 中去定位路徑字符串中下標爲pathLength+1的字符。
 *    如果4個相鄰的格子都沒有匹配字符串中下標爲pathLength+1的字符,則表明當前路徑字符串中
 * 下標爲pathLength的字符在矩陣中定位不正確,我們需要回到前一個字符(pathLength-1),然後重新定位。
 *    一直重複這個過程,知道路徑字符串上的所有字符都在矩陣中找到合適的位置(此時 str[pathLength] == '\0')
 */ 
bool Judge(const char *matrix, int rows, int cols, int row, int col, 
                const char *str, int pathLength, bool *visited) {
    if (str[pathLength] == '\0')                    // 遍歷完路徑字符串,說明路徑字符串上的所有字符都在矩陣中找到合適的位置了
        return true;
    
    bool hasPath = false;
    int index = row * cols + col;  
    if (row >= 0 && row < rows && col >= 0 && col < cols 
            && matrix[index] == str[pathLength]
                && !visited[index]) {    // str[pathLength]字符找到了
        visited[index] = true;           // 並將找到的這個矩陣元素標識爲已訪問
        hasPath = Judge(matrix, rows, cols, row, col-1, str, pathLength+1, visited)
               || Judge(matrix, rows, cols, row, col+1, str, pathLength+1, visited)  
               || Judge(matrix, rows, cols, row-1, col, str, pathLength+1, visited) 
               || Judge(matrix, rows, cols, row+1, col, str, pathLength+1, visited);
        // 沒有匹配,回退上一個位置
        visited[index] = false;
    }
    return hasPath;
}

bool hasPath(char *matrix, int rows, int cols, char *str) {
    if (matrix == NULL || rows < 1 || cols < 1 || str == NULL) 
        return false;
    bool *visited = new bool[rows * cols];  // 創建一個bool類型的動態數組,返回數組的第一個元素類型的指針
    memset(visited, 0, rows * cols);        // 將bool數組元素都初始化爲0,即false

    int pathLength = 0;                     // 路徑字符串下標
    for (int row = 0; row < rows; ++row) {
        for (int col = 0; col < cols; ++col) {
            if (Judge(matrix, rows, cols, row, col, str, pathLength, visited)) {
                return true;
            }
        }
    }

    delete[] visited;
    return false;
}

int main() {
    char matrix[MAX_SIZE], str[MAX_SIZE];
    int rows, cols, res;
    printf("Please input the row number:\n");
    scanf("%d", &rows);
    printf("Please input the col number:\n");
    scanf("%d", &cols);
    getchar();                  // 讀取緩衝區中的回車
    printf("Please input the matrix:\n");
    scanf("%s", matrix);        
    printf("Please input the search path:\n");
    getchar();                  // 讀取緩衝區中的回車
    scanf("%s", str);
    res = hasPath(matrix, rows, cols, str);
    if (res) {
        printf("Matirx has this path!\n");
    } else {
        printf("Matirx do not has this path!\n");
    }
    // getchar();
    return 0;
}

7. 機器人的運動範圍

題目描述
​ 地上有一個m行和n列的方格。一個機器人從座標0, 0的格子開始移動,每一次只能
向左,右,上,下四個方向移動一格,但是不能進入行座標和列座標的數位之和大於k
的格子。 例如,當k爲18時,機器人能夠進入方格(35,37),因爲3+5+3+7 = 18。
但是,它不能進入方格(35,38),因爲3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

思路:
​ 回溯的思想(類似於DFS深度優先搜索,先序遍歷,結合國際象棋看)

#include <stdio.h>
#include <stdlib.h>

/**
 * 計算整數各位之和
 */
int GetDigistSum(int number) {
    int sum = 0;
    while (number) {
        sum += (number % 10);
        number /= 10;
    }
    return sum;
}

/**
 * DFS 深度優先遍歷
 */ 
int Moving(int threshold, int rows, int cols, int x, int y, int *visited) {
    int count = 0;
    if (x >= 0 && x < rows && y >= 0 && y < cols 
            && (GetDigistSum(x) + GetDigistSum(y)) <= threshold 
                && visited[x * cols + y] == 0) {
        visited[x * cols + y] = 1;
        count = 1 + Moving(threshold, rows, cols, x-1, y, visited)
                  + Moving(threshold, rows, cols, x+1, y, visited)
                  + Moving(threshold, rows, cols, x, y-1, visited)
                  + Moving(threshold, rows, cols, x, y+1, visited);
    }
    return count;
}

/**
 * 初始化訪問標誌矩陣,並從 (0, 0) 開始遍歷
 */ 
int MovingCount(int threshold, int rows, int cols) {
    if (threshold < 0 || rows < 0 || cols < 0)
        return 0;
    int *visited = (int*)malloc(sizeof(int) * rows * cols);
    for (int i = 0; i < rows * cols; i++) 
        *(visited + i) = 0;
    int count = Moving(threshold, rows, cols, 0, 0, visited);
    free(visited);
    return count;
}

int main() {
    int rows, cols, threshold = 18;
    scanf("%d %d", &rows, &cols);
    scanf("%d", &threshold);
    printf("%d\n", MovingCount(threshold, rows, cols));
    return 0;
}

8. 全排列 [leetcode-46]

123的全排列有123、132、213、231、312、321這六種。首先考慮213和321
這二個數是如何得出的。顯然這二個都是123中的1與後面兩數交換得到的。然後
可以將123的第二個數和每三個數交換得到132。同理可以根據213和321來得231
和312。因此可以知道——全排列就是從第一個數字起每個數分別與它後面的數字交
換。找到這個規律後,遞歸的代碼就很容易寫出來了

#include <stdio.h>
/* 交換兩個數據 */
void Swap(int* a, int* b) {
    int c = *a;
    *a = *b;
    *b = c;
}

void FullyArrange(int str[], int index, int str_len) {
    if (index == str_len) {
        /* 輸出當前的排列 */
        for (int i = 0; i < str_len; i++) {
            printf("%d ", str[i]);
        }
        printf("\n");
    } else {
        for (int j = index; j < str_len; j++) {     // 第index個數分別與它後面的數字交換就能得到新的排列
            Swap(&str[index], &str[j]);
            FullyArrange(str, index + 1, str_len);
            Swap(&str[index], &str[j]);             // 保證每一層遞歸後保持上一層的順序  
        }
    }
}
int main() {
    int a[] = {1, 2, 3};
    int str_len = sizeof(a) / sizeof(int);
    FullyArrange(a, 0, str_len);
    return 0;
}

9. 全排列II [leetcode-47]

給定一個可包含重複數字的序列,返回所有不重複的全排列。

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

#include <iostream>
#include <vector>
#include <set>
using namespace std;

void Swap(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

void permuteUniqueDFS(vector<int> &nums, int index, set<vector<int>> &res_set) {
    if (index == nums.size()) {
        res_set.insert(nums);
    }
    for (int i = index; i < nums.size(); i++) {
        Swap(nums[i], nums[index]);
        permuteUniqueDFS(nums, index + 1, res_set);
        Swap(nums[i], nums[index]);
    }
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
    vector<vector<int>> res;
    set<vector<int>> res_set;
    permuteUniqueDFS(nums, 0, res_set);
    for (set<vector<int>>::iterator iter = res_set.begin(); iter != res_set.end(); iter++) {
        res.push_back(*iter);
    }
    return res;
}

int main() {
    vector<int> nums;
    int x;
    while (cin >> x && x != -1) {
        nums.push_back(x);
    }
    vector<vector<int>> res = permuteUnique(nums);
    for (int i = 0; i < res.size(); i++) {
        for (int j = 0; j < res[0].size(); j++) {
            cout << res[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

10. 組合 [leetcode-77]

給定兩個整數 n 和 k,返回 1 … n 中所有可能的 k 個數的組合。

輸入: n = 4, k = 2
輸出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

#include <iostream>
#include <vector>
using namespace std;

/**
 * 像這種要求出所有結果的集合,一般都是用DFS調用遞歸來解(結合全排列等)
 */ 
void DFS(int n, int k, int level, vector<int> &out, vector<vector<int>> &res) {
    if (out.size() == k) {
        res.push_back(out);
        return;
    }
    for (int i = level; i <= n; i++) {
        out.push_back(i);
        DFS(n, k, i + 1, out, res);
        out.pop_back();
    }
}

vector<vector<int>> combine(int n, int k) {
    vector<vector<int>> res;
    vector<int> out;
    DFS(n, k, 1, out, res);
    return res;
}


int main() {
    int n, k;
    cin >> n >> k;
    vector<vector<int>> nums = combine(n, k);
    for (vector<vector<int>>::iterator iter = nums.begin(); iter != nums.end(); iter++) {
        vector<int>::iterator it = (*iter).begin();
        for (; it != (*iter).end(); it++) {
            cout << *it << " ";
        }
        cout << endl;
    }
    return 0;
}

11. 電話號碼字母組合 [leetcode-17]

給定一個僅包含數字 2-9 的字符串,返回所有它能表示的字母組合。
給出數字到字母的映射如下(與電話按鍵相同)。注意 1 不對應任何字母。
輸入:“23”
輸出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].

思路:
​ 我們用遞歸 Recursion 來解,我們需要建立一個字典,用來保存每個數字所代表的字符串,
然後我們還需要一個變量 level,記錄當前生成的字符串的字符個數,實現套路和上述那些
題十分類似。在遞歸函數中我們首先判斷 level,如果跟 digits 中數字的個數相等了,
我們將當前的組合加入結果 res 中,然後返回。否則我們通過 digits 中的數字到 dict
中取出字符串,然後遍歷這個取出的字符串,將每個字符都加到當前的組合後面,並調用遞歸
函數即可

#include <iostream>
#include <string>
#include <vector>
using namespace std;

void letterCombinationsDFS(string &digist, vector<string> &dict, int index, string out, vector<string> &res) {
    if (index == digist.size()) {
        res.push_back(out);
        return;
    }
    string str = dict[digist[index] - '0'];
    for (int i = 0; i < str.size(); i++) {
        letterCombinationsDFS(digist, dict, index + 1, out + str[i], res);
    }
}

vector<string> letterCombinations(string digits) {
    if (digits.empty()) return {};
    vector<string> res;
    vector<string> dict{"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    letterCombinationsDFS(digits, dict, 0, "", res);
}

int main() {
    string digits;
    cin >> digits;
    vector<string> res = letterCombinations(digits);
    for (vector<string>::iterator iter = res.begin(); iter != res.end();
         iter++) {
        cout << *iter << endl;
    }
    return 0;
}

12. 組合總和 [leetcode-39]

給定一個無重複元素的數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和爲 target 的組合。
candidates 中的數字可以無限制重複被選取。

說明:
所有數字(包括 target)都是正整數。
解集不能包含重複的組合。
示例 1:
輸入: candidates = [2,3,6,7], target = 7,
所求解集爲:
[
[7],
[2,2,3]
]

示例 2:
輸入: candidates = [2,3,5], target = 8,
所求解集爲:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

思路:和組合,排列的那些題目都是一個套路
​ 需要另寫一個遞歸函數,這裏我們新加入三個變量,index記錄當前的遞歸到的下標,out爲一個解,res保存所有已經得到的解,每次調用新的遞歸函數時,此時的target要減去當前數組的的數

#include <iostream>
#include <vector>
#include <string>
using namespace std;

void combinationSumDFS(vector<int> candidates, int target, int index, vector<int> &out, vector<vector<int>> &res) {
    if (target < 0) return;
    if (target == 0) {
        res.push_back(out);
        return;
    }
    for (int i = index; i < candidates.size(); i++) {
        out.push_back(candidates[i]);
        combinationSumDFS(candidates, target - candidates[i], i, out, res);
        // 注意,這裏傳入的是 i 而不是 index,保證以前重複使用過的不再回溯使用,即保證沒有重複的組合
        out.pop_back();     // 如果加上下一個之後大於target了,則回溯到上一步
    }

}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> res;
    vector<int> out;
    combinationSumDFS(candidates, target, 0, out, res);
    return res;
}

int main() {
    vector<vector<int>> res;
    vector<int> candidates;
    int target, x;
    while (cin >> x && x != -1) {
        candidates.push_back(x);
    }
    cin >> target;
    res = combinationSum(candidates, target);
    for (int i = 0; i < res.size(); i++) {
        cout << "[";
        for (int j = 0; j < res[i].size(); j++) {
            cout << res[i][j] << " ";
        }
        cout << "]" << endl;
    }
    return 0;
}

13. 組合總和II [leetcode-40]

給定一個數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使
數字和爲 target 的組合。candidates 中的每個數字在每個組合中只能使用一次。

說明:

所有數字(包括目標數)都是正整數。
解集不能包含重複的組合。
示例 1:

輸入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集爲:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:

輸入: candidates = [2,5,2,1,2], target = 5,
所求解集爲:
[
[1,2,2],
[5]
]

思路:和組合,排列的那些題目都是一個套路
​ 需要另寫一個遞歸函數,這裏我們新加入三個變量,index記錄當前的遞歸到的下標,
out爲一個解,res保存所有已經得到的解,每次調用新的遞歸函數時,此時的target
要減去當前數組的的數

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void combinationSum2DFS(vector<int> &candidates, int target, int index, vector<int> &out, vector<vector<int>> &res) {
    if (target < 0) return;
    if (target == 0) {
        res.push_back(out);
        return;
    }
    for (int i = index; i < candidates.size(); i++) {
        if (i > index && candidates[i] == candidates[i-1]) continue;   // 保證res中沒有重複的
        out.push_back(candidates[i]);
        combinationSum2DFS(candidates, target - candidates[i], i + 1, out, res); 
        // i + 1,就不會重複使用當前數字了
        out.pop_back();
    }
}

vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
    vector<vector<int>> res;
    vector<int> out;
    sort(candidates.begin(), candidates.end());
    combinationSum2DFS(candidates, target, 0, out, res);
    return res;
}

int main() {
    vector<vector<int>> res;
    vector<int> candidates;
    int target, x;
    while (cin >> x && x != -1) {
        candidates.push_back(x);
    }
    cin >> target;
    res = combinationSum2(candidates, target);

    for (vector<vector<int>>::iterator iter = res.begin(); iter != res.end(); iter++) {
        cout << "[";
        for (vector<int>::iterator it = (*iter).begin(); it != (*iter).end(); it++) {
            cout << *it << " ";
        }
        cout << "]" << endl;
    }
    return 0;
}

14. 組合總和III [leetcode-216]

找出所有相加之和爲 n 的 k 個數的組合。組合中只允許含有 1 - 9 的正整數,並且每種組合中不存在重複的數字。

說明:
所有數字都是正整數。
解集不能包含重複的組合。

示例 1:
輸入: k = 3, n = 7
輸出: [[1,2,4]]

示例 2:
輸入: k = 3, n = 9
輸出: [[1,2,6], [1,3,5], [2,3,4]]

#include <iostream>
#include <vector>
using namespace std;

void combinationSum3DFS(int k, int target, int index, vector<int> &out, vector<vector<int>> &res) {
    if (target < 0) return;
    if (target == 0 && out.size() == k) {
        res.push_back(out);
        return;
    }
    for (int i = index; i <= 9; i++) {
        out.push_back(i);
        combinationSum3DFS(k, target - i, i + 1, out, res); // 注意,這裏要傳入 i + 1,保證不適用重複的元素
        out.pop_back();
    }
}

vector<vector<int>> combinationSum3(int k, int n) {
    vector<vector<int>> res;
    vector<int> out;
    combinationSum3DFS(k, n, 1, out, res);
    return res;
}

int main() {
    int k, n;
    cin >> k >> n;
    vector<vector<int>> res = combinationSum3(k, n);
    for (vector<vector<int>>::iterator iter = res.begin(); iter != res.end(); iter++) {
        cout << "[";
        for (vector<int>::iterator it = (*iter).begin(); it != (*iter).end(); it++) {
            cout << *it << " ";
        }
        cout << "]" << endl;
    }
    return 0;
}

15. 子集 [leetcode-78]

給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。

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

思想:
​ 其實就是將長度爲0-nums.size() 長度的組合都列出來就可以。

#include <iostream>
#include <vector>
using namespace std;

void subsetsDFS(vector<int> &nums, int index, int target_size, vector<int> &out, vector<vector<int>> &res) {
    if (out.size() == target_size) {
        res.push_back(out);
        return;
    }
    for (int i = index; i < nums.size(); i++) {
        out.push_back(nums[i]);
        subsetsDFS(nums, i + 1, target_size, out, res);
        out.pop_back();
    }
}

vector<vector<int>> subsets(vector<int>& nums) {
    vector<vector<int>> res;
    vector<int> out;
    res.push_back({});
    for (int i = 1; i <= nums.size(); i++) {
        subsetsDFS(nums, 0, i, out, res);
    }
    return res;
}

int main() {
    vector<vector<int>> res;
    vector<int> nums;
    int x;
    while (cin >> x && x != -1) {
        nums.push_back(x);
    }
    res = subsets(nums);
    for (vector<vector<int>>::iterator iter = res.begin(); iter != res.end(); iter++) {
        cout << "[";
        for (vector<int>::iterator it = (*iter).begin(); it != (*iter).end(); it++) {
            cout << *it << " ";
        }
        cout << "]" << endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章