代码随想录算法训练营第三十天| 332.重新安排行程、51. N皇后、37. 解数独

本文介绍了LeetCode中的三道经典问题——重新安排行程、N皇后和解数独,分别使用排列问题的回溯法和基于地图信息的优化策略,以及两种不同的回溯实现来解决。这些方法展示了如何利用回溯在约束条件下找到所有可能的解决方案,并优化搜索效率。
摘要由CSDN通过智能技术生成

LeetCode 332 重新安排行程

题目链接:332

方法1 直接当作排列问题

思路:看作是vector<vector<string>>的排列问题,只不过排列时需要满足上一节点的终点等于当前节点的起点这一条件。因此,剪枝有2个条件:

  • 当前机票的起点不等于上张机票的终点
  • 当前机票存在树枝重复,即used数组记录

当存在多种有效行程时,选择字典排序小的,因此可以先对机票数组按每张机票的终点进行字典排序,从而保证在for循环时,字典排序小的目的地优先处理。这样,搜索到的第一条有效行程即是字典排序最小的有效行程,通过给回溯函数设置返回值提前返回。
代码:

class Solution {
public:

    vector<string> results;
    vector<bool> used;

    bool backtracking(vector<vector<string>>& tickets) {
        if (results.size() == tickets.size()+1) {
            return true;
        }

        for (int i=0; i<tickets.size(); ++i) {
            if (used[i]) continue;
            if (tickets[i][0] != results.back()) continue;
            results.push_back(tickets[i][1]);
            used[i] = true;
            if (backtracking(tickets)) return true;
            used[i] = false;
            results.pop_back();
        }
        return false;
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        results.clear();
        used = vector<bool>(tickets.size(), false);
        sort(tickets.begin(), tickets.end(), 
            [](const vector<string>& s1, const vector<string>& s2) {
                return s1[1] < s2[1];
            });
        results.push_back("JFK");
        backtracking(tickets);
        return results;
    }
};

方法2 使用map

思路:不同于在每层递归中都遍历tickets 寻找下一可行的机票,先通过一次遍历将起点终点信息记录在unordered_map<string, map<string, int>>中,key记录起点信息,valuemap<string, int>记录每一起点对应的可行终点及可行次数,次数为该起点终点的机票张数。并且map本身有序,默认递增,即字典值小的在前。这样,每层递归时,可通过起点快速查询可行的终点即次数,避免了tickets的重复遍历。
代码:

class Solution {
public:
    unordered_map<string, map<string, int>> ticket_map;

    bool backtracking(vector<string>& results, int path_len) {
        if (results.size() == path_len) return true;

        for (auto& next : ticket_map[results.back()]) {
            if (next.second == 0) continue;
            results.push_back(next.first);
            next.second--;
            if (backtracking(results, path_len)) return true;
            next.second++;
            results.pop_back();
        }
        return false;
    }

    vector<string> findItinerary(vector<vector<string>>& tickets) {
        ticket_map.clear();
        for (int i=0; i<tickets.size(); ++i) {
            ticket_map[tickets[i][0]][tickets[i][1]]++;
        }
        vector<string> result = {"JFK"};
        backtracking(result, tickets.size()+1);
        return result;
    }
};

LeetCode 51 N 皇后

题目链接:51

方法1 记录使用过的列和斜线

思路:每行选取一个位置为Q,这样不会有多个皇后处于同一行,此时递归层数等于n。选取col时,通过记录的使用过的列和斜线used来判断该位置是否可行。具体而言,当放置一个皇后(col, row)时,列可直接记录;两条对角线分别为斜率为 1 1 1 − 1 -1 1 的直线,因此可算出该位置对角线的截距为row-colcol-row,通过记录使用过的截距来记录不可行的对角线。
代码:

class Solution {
public:
    vector<string> path;
    vector<vector<string>> result;

    vector<bool> used_x;
    vector<bool> used_pos_diag;
    vector<bool> used_neg_diag;

    void backtracking(int y, int n) {
        if (path.size() == n) {
            result.push_back(path);
            return;
        }

        for (int x=0; x<n; ++x) {
            if (used_x[x]) continue;
            int pos_diag_offset = y-x;
            int neg_diag_offset = y+x;
            if (used_pos_diag[pos_diag_offset+(n-1)]) continue;
            if (used_neg_diag[neg_diag_offset]) continue;
            string row(n, '.');
            row[x] = 'Q';
            path.push_back(row);
            used_x[x] = true;
            used_pos_diag[pos_diag_offset+(n-1)] = true;
            used_neg_diag[neg_diag_offset] = true;
            backtracking(y+1, n);
            used_neg_diag[neg_diag_offset] = false;
            used_pos_diag[pos_diag_offset+(n-1)] = false;
            used_x[x] = false;
            path.pop_back();
        }
    }

    vector<vector<string>> solveNQueens(int n) {
        used_x = vector<bool>(n, false);
        used_pos_diag = vector<bool>(2*n-1, false);
        used_neg_diag = vector<bool>(2*n-1, false);
        path.clear();
        result.clear();
        backtracking(0, n);
        return result;
    }
};

方法2 遍历判断列和斜对角

思路:在判断位置(col,row)时,遍历[0,row-1]行中对应列和斜对角的元素是否有Q
代码:

class Solution {
public:
    vector<vector<string>> result;

    bool isValid(int n, int row, int col, vector<string>& chessboard) {
        for (int i=0; i<row; i++) {
            if (chessboard[i][col] == 'Q') return false;
        }
        for (int r=row-1, c=col-1; r>=0 && c>=0; r--, c--) {
            if (chessboard[r][c] == 'Q') return false;
        }
        for (int r=row-1, c=col+1; r>=0 && c<n; r--, c++) {
            if (chessboard[r][c] == 'Q') return false;
        }
        return true;
    }

    void backtracking(int n, int row, vector<string>& chessboard) {
        if (row == n) {
            result.push_back(chessboard);
            return;
        }

        for (int i=0; i<n; i++) {
            if (!isValid(n, row, i, chessboard)) continue;
            chessboard[row][i] = 'Q';
            backtracking(n, row+1, chessboard);
            chessboard[row][i] = '.';
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<string> chessboard(n, string(n, '.'));
        backtracking(n, 0, chessboard);
        return result;
    }
};

LeetCode 37 解数独

题目链接:37

方法1 使用一个一维index递归二维表

思路:递归函数中采用一个一维的index来指示当前网格,处理完成后在下层递归中处理下个网格。若当前网格非空,则无需填数,直接进入下层递归。由于数独找到一个解即可返回,因此递归函数需要返回值bool
代码:

class Solution {
public:
    bool isValid(int row, int col, char num, vector<vector<char>>& board) {
        for (int r=0; r<board.size(); r++) {
            if (board[r][col] == num) return false;
        }
        for (int c=0; c<board.size(); c++) {
            if (board[row][c] == num) return false;
        }

        int hgrid_row = row / 3;
        int hgrid_col = col / 3;
        for (int r=hgrid_row*3; r<hgrid_row*3+3; r++) {
            for (int c=hgrid_col*3; c<hgrid_col*3+3; c++) {
                if (board[r][c] == num) return false;
            }
        }
        return true;
    }

    bool backtracking(int index, vector<vector<char>>& board) {
        if (index == board.size() * board.size()) {
            return true;
        }

        int row = index / 9;
        int col = index % 9;
        if (board[row][col] != '.') {
            if (backtracking(index+1, board)) return true;
            return false;
        }

        for (int i=1; i<=9; i++) {
            if (!isValid(row, col, '0'+i, board)) continue;
            board[row][col] = '0'+i;
            if (backtracking(index+1, board)) return true;
            board[row][col] = '.';
        }
        return false;
    }

    void solveSudoku(vector<vector<char>>& board) {
        backtracking(0, board);
    }
};

方法2 二维递归

思路:在每层递归中,都从头开始,找到第一个空格,填数,再进入下层递归找下一个数。
代码:

class Solution {
public:
    bool isValid(int row, int col, char num, vector<vector<char>>& board) {
        for (int r=0; r<board.size(); r++) {
            if (board[r][col] == num) return false;
        }
        for (int c=0; c<board.size(); c++) {
            if (board[row][c] == num) return false;
        }

        int hgrid_row = row / 3;
        int hgrid_col = col / 3;
        for (int r=hgrid_row*3; r<hgrid_row*3+3; r++) {
            for (int c=hgrid_col*3; c<hgrid_col*3+3; c++) {
                if (board[r][c] == num) return false;
            }
        }
        return true;
    }

    bool backtracking(vector<vector<char>>& board) {
        for (int r=0; r<board.size(); ++r) {
            for (int c=0; c<board[r].size(); ++c) {
                if (board[r][c] != '.') continue;
                for (int i=1; i<=9; ++i) {
                    if (!isValid(r, c, '0'+i, board)) continue;
                    board[r][c] = '0'+i;
                    if (backtracking(board)) return true;
                    board[r][c] = '.';
                }
                return false;
            }
        }
        return true;
    }

    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值