LC-单词搜索、分割回文串、N皇后、搜索插入位置、搜索二维矩阵

news/2025/2/22 2:16:24

单词搜索

使用 回溯法 来解决。回溯法适合用于这种路径搜索问题,我们需要在网格中寻找单词,并且每个字符都只能使用一次。

思路:

  1. 递归搜索:我们可以从网格中的每个单元格开始,进行深度优先搜索(DFS),并通过递归逐个匹配单词中的字符。每次匹配时,我们需要检查当前位置是否符合条件,并标记访问过的单元格,避免重复使用。

  2. 方向控制:相邻的单元格可以是上下左右四个方向。因此,我们需要在四个方向上进行递归搜索。

  3. 回溯:当在某个方向无法继续时,需要回溯并尝试其他方向。

具体步骤:

  1. 从网格的每个单元格开始搜索。
  2. 每次递归时,检查当前字符是否与单词的当前字符匹配。
  3. 递归到下一个字符时,需要标记当前单元格为已访问,并确保不重复使用该单元格。
  4. 如果找到整个单词,返回 true,否则继续尝试其他路径。
  5. 如果无法找到单词,则返回 false
class Solution {
    public boolean exist(char[][] board, String word) {
        if(board == null || board.length == 0 || word == null || word.length() == 0){
            return false;
        }

        //遍历每一个点,尝试从该点开始搜索
        for(int i = 0;i < board.length;i++){
            for(int j = 0;j < board[0].length;j++){
                if(dfs(board,word,i,j,0)){
                    return true;
                }
            }
        }

        return false;
    }

    private boolean dfs(char[][] board,String word,int i,int j,int index){
        //如果已经找到所有的字符
        if(index == word.length()){
            return true;
        }

        //边界条件:检查当前位置是否越界,或者字符是否匹配
        if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != word.charAt(index)){
            return false;
        }

        //标记当前位置为已访问,避免重复使用
        char temp = board[i][j];
        board[i][j] = '#';//用一个不可能的字符标记已访问

        //向四个方向递归搜索
        boolean found = dfs(board,word,i + 1,j,index + 1) ||
                        dfs(board,word,i - 1,j,index + 1) ||
                        dfs(board,word,i,j + 1,index + 1) ||
                        dfs(board,word,i,j - 1,index + 1);

        //恢复当前状态
        board[i][j] = temp;

        return found;
    }
}

分割回文串

思路:

  1. 回文判断:首先需要能够判断一个字符串是否为回文串。一个字符串是回文串当且仅当它从前往后和从后往前的字符顺序相同。

  2. 递归与回溯:可以使用递归的方式来进行分割,递归的过程中将字符串分割成多个子串。如果某个子串是回文串,那么继续对剩余部分进行分割,直到整个字符串都被分割完。

  3. 剪枝优化:在递归过程中,如果某个子串不是回文串,则不继续往下搜索,直接回溯。

解决步骤:

  1. 判断回文:在递归过程中,我们需要一个函数来判断当前子串是否为回文。
  2. 回溯分割:从字符串的第一个字符开始,尝试每个位置作为分割点,检查每个子串是否是回文。如果是回文,则将其加入到当前路径中,递归处理剩余部分。
  3. 终止条件:当处理到字符串的末尾时,当前路径中的所有子串都是回文串,加入到结果列表中。
class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> result = new ArrayList<>();
        List<String> current = new ArrayList<>();
        backtrack(result,current,s,0);
        return result;
    }

    private void backtrack(List<List<String>> result,List<String> current,String s,int start){
        //递归的终止条件:如果start已经遍历到字符串的末尾,说明当前的分割已经完成
        if(start == s.length()){
            result.add(new ArrayList<>(current));
            return;
        }

        //从每一个位置判断是否是回文
        for(int end = start + 1;end <= s.length();end++){
            String substring = s.substring(start,end);
            if(isPalindrome(substring)){
                current.add(substring);//如果是回文,将子串加入当前路径
                backtrack(result,current,s,end);//递归处理剩余部分
                current.remove(current.size() - 1);//回溯,移除最后一个子串
            }
        }
    }


    //判断一个字符串是否是回文串
    private boolean isPalindrome(String str){
        int left = 0,right = str.length() - 1;
        while(left < right){
            if(str.charAt(left) != str.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

N皇后

  1. 回溯法:我们可以使用回溯法来逐行放置皇后,确保每次放置时不会和之前放置的皇后发生冲突。

  2. 冲突检测

    1. 每一行只能有一个皇后。
    2. 每一列只能有一个皇后。
    3. 每条对角线只能有一个皇后。可以通过行列坐标的差和和来区分两条对角线。
  3. 约束条件

    1. 我们使用三个集合来记录哪些列、哪些主对角线(row - col)、哪些副对角线(row + col)被占用。
    2. 每次递归时,尝试在当前行的每一列放置皇后,如果没有冲突,则继续在下一行放置,直到所有行都被填满。
  4. 回溯过程

    1. 通过递归尝试不同的列位置放置皇后,确保每个皇后不会和其他皇后发生冲突。
    2. 当所有皇后都成功放置后,将当前的棋盘状态加入结果列表。
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> result = new ArrayList<>();
        //用来记录哪些列、主对角线、副对角线已经被占用
        Set<Integer> cols = new HashSet<>();
        Set<Integer> diag1 = new HashSet<>();
        Set<Integer> diag2 = new HashSet<>();

        //当前棋盘的状态
        List<String> board = new ArrayList<>();

        backtrack(n,0,cols,diag1,diag2,board,result);

        return result;
    }

    private void backtrack(int n,int row,Set<Integer> cols,Set<Integer> diag1,Set<Integer> diag2,List<String> board,List<List<String>> result){
        //如果所有行都已经放置了一个皇后,说明找到一个解法
        if(row == n){
            result.add(new ArrayList<>(board));
            return;
        }

        //尝试在当前行的每一列放置皇后
        for(int col = 0;col < n;col++){
            //检查是否冲突:列、主对角线、副对角线
            if(cols.contains(col) || diag1.contains(row - col) || diag2.contains(row + col)){
                continue;
            }

            //放置皇后
            cols.add(col);
            diag1.add(row - col);
            diag2.add(row + col);

            //构建当前行的棋盘状态
            StringBuilder currentRow = new StringBuilder();

            for(int i = 0;i < n;i++){
                if(i == col){
                    currentRow.append('Q');
                }else{
                    currentRow.append('.');
                }
            }

            board.add(currentRow.toString());

            //递归放置下一行的皇后
            backtrack(n,row + 1,cols,diag1,diag2,board,result);

            //回溯,撤销选择
            cols.remove(col);
            diag1.remove(row - col);
            diag2.remove(row + col);
            board.remove(board.size() - 1);
        }
    }
}

搜索插入位置

思路:

  1. 二分查找:由于数组已经排序,可以使用二分查找来定位目标值。如果找到了目标值,返回其索引;如果没有找到,则返回目标值应该插入的位置。
  2. 插入位置:二分查找会帮助我们找到一个合适的位置,使得目标值可以插入数组中,且保持数组的排序。插入位置是指目标值应插入的最小位置,确保数组仍然有序。

步骤:

  1. 初始化两个指针 leftright,分别指向数组的开始和结束。
  2. 在每次迭代时,计算中间索引 mid,并与目标值进行比较:
    • 如果 nums[mid] == target,说明找到了目标值,直接返回 mid
    • 如果 nums[mid] < target,说明目标值在右半部分,更新 leftmid + 1
    • 如果 nums[mid] > target,说明目标值在左半部分,更新 rightmid - 1
  3. 如果循环结束时没有找到目标值,left 将是目标值应该插入的位置。
class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0,right = nums.length - 1;

        //二分查找
        while(left <= right){
            int mid = left + (right - left) / 2;

            if(nums[mid] == target){
                return mid;//找到目标值
            }else if(nums[mid] < target){
                left = mid + 1;//目标值在右边
            }else{
                right = mid - 1;//目标值在左边
            }
        }

        //如果没有找到目标值,left是插入位置
        return left;
    }
}

搜索二维矩阵

  1. 行和列的结构

    • 由于每行是按非严格递增排列的,因此我们可以对每行进行二分查找,或者使用其他方法逐行查找。
    • 由于每行的第一个元素大于前一行的最后一个元素,矩阵实际上满足一个类似 "升序排列" 的条件,整个矩阵可以看作一个有序的数组。
  2. 优化的二分查找

    • 我们可以将二维矩阵转换为一个一维的排序数组,通过二分查找来快速定位目标值。
    • 假设矩阵有 mn 列,那么元素可以被看作一个有 m * n 个元素的数组,索引 i 对应的矩阵元素是 matrix[i / n][i % n]。这样可以把二维查找转化为一维查找。

时间复杂度为 O(log(m * n))

class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        int low = 0, high = m * n - 1;
        
        while (low <= high) {
            int mid = (high - low) / 2 + low;
            int x = matrix[mid / n][mid % n];
            if (x < target) {
                low = mid + 1;
            } else if (x > target) {
                high = mid - 1;
            } else {
                return true;
            }
        }
        return false;
    }
}

http://www.niftyadmin.cn/n/5861524.html

相关文章

分布式 IO 模块:水力发电设备高效控制的关键

在能源领域不断追求高效与可持续发展的今天&#xff0c;水力发电作为一种清洁、可再生的能源形式&#xff0c;备受关注。而要实现水力发电设备的高效运行&#xff0c;精准的控制技术至关重要。分布式 IO 模块&#xff0c;正悄然成为水力发电设备高效控制的核心力量。 传统挑战 …

B+树作为数据库索引结构的优势对比

MySQL作为数据库&#xff0c;它的功能就是做数据存储和数据查找&#xff1b;使用B树作为索引结构是为了实现高效的查找、插入和删除操作。 B树的查找、插入、删除的复杂度都为 O(log n)&#xff0c;它是一个多叉树的结构&#xff0c;能兼顾各种操作的效率的数据结构。如果使用…

企业内部真题

文章目录 前端面试题:一个是铺平的数组改成树的结构问题一解析一问题一解析二前端面试题:for循环100个接口,每次只调3个方法一:使用 `async/await` 和 `Promise`代码解释(1):代码解释(2):1. `fetchApi` 函数2. `concurrentFetch` 函数3. 生成 100 个接口地址4. 每次并…

thread---基本使用和常见错误

多线程基础 一、C多线程基础 线程概念&#xff1a;线程是操作系统能够调度的最小执行单元&#xff0c;同一进程内的多个线程共享内存空间头文件&#xff1a;#include <thread>线程生命周期&#xff1a;创建->执行->销毁&#xff08;需显式管理&#xff09;注意事…

如何将MySQL数据库迁移至阿里云

将 MySQL 数据库迁移至阿里云可以通过几种不同的方法&#xff0c;具体选择哪种方式取决于你的数据库大小、数据复杂性以及对迁移速度的需求。阿里云提供了多种迁移工具和服务&#xff0c;本文将为你介绍几种常见的方法。 方法一&#xff1a;使用 阿里云数据库迁移服务 (DTS) 阿…

C语言基础系列【15】union 共用体

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…

前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析

本文以真实业务场景为背景&#xff0c;深入剖析前端PDF转图片的 7大核心指标 &#xff0c;通过3000字详解5种方案对比性能压测数据&#xff0c;输出可复用的技术调研方法论。 一、技术调研认知误区与破局之道 1.1 需求理解典型翻车现场 // 错误案例&#xff1a;未明确需求边界…

【系统架构设计师】需求工程

目录 1. 说明2. 软件需求3. 需求阶段4. 需求管理5. 例题5.1 例题1 1. 说明 1.软件需求目前并没有统一的定义&#xff0c;但都包含以下几方面的内容。2.用户解决问题或达到目标所需条件或权能&#xff08;Capability&#xff09;。系统或系统部件要满足合同、标准、规范或其他正…