【4.2】图搜索算法-DFS和BFS解单词拆分

news2024/11/24 3:44:46

一、题目

        给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分
为一个或多个在字典中出现的单词。
 
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

 

示例 1:
输入:
s = "leetcode ",
wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:
输入:
s = " applepenapple ",
wordDict = [" apple ", "pen"]
输出: true
解释: 返回 true 因为 " applepenapple " 可以被拆分成 " apple pen apple"。
注意你可以重复使用字典中的单词。

示例 3:输入:
s = " catsandog",
wo rdDi c t = [" cats ", "dog", " sand", "and", "cat"]
输出: false

 

二、求解思路

前面我们已经讲解过这道题,使用的是动态规划方法,具体可以参考动态规划解单词拆分。今天我们将分别使用DFS(深度优先搜索)和BFS(广度优先搜索)来解决这个问题。

题目要求将字符串拆分,并判断拆分后的子串是否都存在于给定的字典中。那么,字符串应该如何拆分呢?我们通过一个例子来详细说明,比如字符串 `"abcd"`,我们可以进行如下拆分:

- `["a", "b", "c", "d"]`
- `["a", "b", "cd"]`
- `["a", "bc", "d"]`
- `["a", "bcd"]`
- `["ab", "c", "d"]`
- `["ab", "cd"]`
- `["abc", "d"]`
- `["abcd"]`

具体拆分方式可以参考下图:

6cadf475196b4c2b823380302fb35945.png

每次截取一个子串,判断它是否存在于字典中。如果该子串不存在于字典中,则继续截取更长的子串进行判断;如果存在于字典中,则递归拆分剩下的子串。这是一个递归的过程。

上述执行过程可以看作是对一棵n叉树的深度优先搜索(DFS)遍历。具体来说,每个节点代表一个子串,节点的子节点代表从该子串开始截取的更长子串。通过递归遍历这棵树,我们可以找到所有符合条件的拆分方式。所以大致代码如下:

#include <string>
#include <vector>
#include <unordered_set>

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict) {
    return dfs(s, wordDict);
}

bool dfs(const std::string& s, const std::vector<std::string>& wordDict) {
    // 最终条件,都截取完了,直接返回true
    if (s.empty()) {
        return true;
    }

    // 开始拆分字符串s
    for (size_t i = 1; i <= s.length(); i++) {
        // 截取子串
        std::string sub = s.substr(0, i);

        // 如果截取的子串不在字典中,继续截取更大的子串
        if (std::find(wordDict.begin(), wordDict.end(), sub) == wordDict.end()) {
            continue;
        }

        // 如果截取的子串在字典中,继续剩下的拆分,如果剩下的可以拆分成
        // 在字典中出现的单词,直接返回true,如果不能则继续
        // 截取更大的子串判断
        if (dfs(s.substr(i), wordDict)) {
            return true;
        }
    }

    // 如果都不能正确拆分,直接返回false
    return false;
}

在上述代码中,递归调用必须有一个终止条件,以避免无限递归。通过观察递归过程,我们可以发现,终止条件是当字符串 s 中的所有字符都被遍历完毕时。此时,说明字符串 s 可以被拆分成若干个子串,并且这些子串都存在于给定的字典中。

为了更清晰地理解这个过程,我们可以通过一个图示来说明:

7b63fb656efe4146bf64f33d4db383bc.png

因为是拆分,所以字符串截取的时候不能有重叠,那么[开始截取的位置]实际上就是上次截取位置的下一个,来看下代码

 

#include <string>
#include <vector>
#include <unordered_set>

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict) {
    return dfs(s, wordDict, 0);
}

// start表示的是从字符串s的哪个位置开始
bool dfs(const std::string& s, const std::vector<std::string>& wordDict, int start) {
    // 字符串中的所有字符都遍历完了,也就是到叶子节点了,说明字符串s可以拆分成
    // 在字典中出现的单词,直接返回true
    if (start == s.length()) {
        return true;
    }

    // 开始拆分字符串s
    for (int i = start + 1; i <= s.length(); i++) {
        // 截取子串
        std::string sub = s.substr(start, i - start);

        // 如果截取的子串不在字典中,继续截取更大的子串
        if (std::find(wordDict.begin(), wordDict.end(), sub) == wordDict.end()) {
            continue;
        }

        // 如果截取的子串在字典中,继续剩下的拆分,如果剩下的可以拆分成
        // 在字典中出现的单词,直接返回true,如果不能则继续
        // 截取更大的子串判断
        if (dfs(s, wordDict, i)) {
            return true;
        }
    }

    // 如果都不能正确拆分,直接返回false
    return false;
}
实际上面代码运行效率很差,这是因为如果字符串s比较长的话,这里会包含大量的重复计算,我们还用上面的图来看下
 

26cdadf66f814086a76265d7646433fc.png

        我们看到红色的就是重复计算,这里因为字符串比较短,不是很明显,当字符串比较长的时候,这里的重复计算非常多。我们可以使用一个变量,来记录计算过的位置,如果之前 判断过,就不在重复判断,直接跳过即可,代码如下

 

三、代码实现

DFS算法解决代码:
#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict);
bool dfs(const std::string& s, const std::vector<std::string>& wordDict, std::unordered_set<int>& indexSet, int start);

int main() {
    std::string s = "leetcode";
    std::vector<std::string> wordDict = {"leet", "code"};

    if (wordBreak(s, wordDict)) {
        std::cout << "The string can be segmented into words from the dictionary." << std::endl;
    } else {
        std::cout << "The string cannot be segmented into words from the dictionary." << std::endl;
    }

    return 0;
}

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict) {
    std::unordered_set<int> indexSet;
    return dfs(s, wordDict, indexSet, 0);
}

// start表示的是从字符串s的哪个位置开始
bool dfs(const std::string& s, const std::vector<std::string>& wordDict, std::unordered_set<int>& indexSet, int start) {
    // 字符串都拆分完了,返回true
    if (start == s.length()) {
        return true;
    }

    for (int i = start + 1; i <= s.length(); i++) {
        // 如果已经判断过了,就直接跳过,防止重复判断
        if (indexSet.find(i) != indexSet.end()) {
            continue;
        }

        // 截取子串,判断是否是在字典中
        std::string sub = s.substr(start, i - start);
        if (std::find(wordDict.begin(), wordDict.end(), sub) != wordDict.end()) {
            if (dfs(s, wordDict, indexSet, i)) {
                return true;
            }
            // 标记为已判断过
            indexSet.insert(i);
        }
    }

    return false;
}

BFS算法解决:

这题除了DFS以外,还可以使用BFS,BFS就是一层一层的遍历,如下图所示
 

34fac3366a86427cb0c2425e3e7be093.png

BFS(广度优先搜索)通常不需要递归,而是使用一个队列来记录每一层需要处理的值。在BFS中,当我们截取子串时,如果截取的子串存在于字典中,我们就记录截取的位置,并在下一层从这个位置的下一个位置继续截取。下面是相应的C++代码实现:

#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>
#include <queue>

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict) {
    // 这里为了提高效率,把list转化为set,因为set的查找效率要比list高
    std::unordered_set<std::string> setDict(wordDict.begin(), wordDict.end());
    // 记录当前层开始遍历字符串s的位置
    std::queue<int> queue;
    queue.push(0);
    int length = s.length();

    while (!queue.empty()) {
        int index = queue.front();
        queue.pop();

        // 如果字符串到遍历完了,直接返回true
        if (index == length) {
            return true;
        }

        for (int i = index + 1; i <= length; i++) {
            // 截取子串,判断是否是在字典中
            std::string sub = s.substr(index, i - index);
            if (setDict.find(sub) != setDict.end()) {
                queue.push(i);
            }
        }
    }

    return false;
}

int main() {
    std::string s = "leetcode";
    std::vector<std::string> wordDict = {"leet", "code"};

    if (wordBreak(s, wordDict)) {
        std::cout << "The string can be segmented into words from the dictionary." << std::endl;
    } else {
        std::cout << "The string cannot be segmented into words from the dictionary." << std::endl;
    }

    return 0;
}
这种也会出现重复计算的情况,所以这里我们也可以使用一个变量来记录下。
 
 
#include <iostream>
#include <string>
#include <vector>
#include <unordered_set>
#include <queue>

bool wordBreak(const std::string& s, const std::vector<std::string>& wordDict) {
    // 这里为了提高效率,把list转化为set,因为set的查找效率要比list高
    std::unordered_set<std::string> setDict(wordDict.begin(), wordDict.end());
    // 记录当前层开始遍历字符串s的位置
    std::queue<int> queue;
    queue.push(0);
    int length = s.length();
    // 记录访问过的位置,减少重复判断
    std::vector<bool> visited(length, false);

    while (!queue.empty()) {
        int index = queue.front();
        queue.pop();

        // 如果字符串都遍历完了,直接返回true
        if (index == length) {
            return true;
        }

        // 如果被访问过,则跳过
        if (visited[index]) {
            continue;
        }

        // 标记为访问过
        visited[index] = true;

        for (int i = index + 1; i <= length; i++) {
            // 截取子串,判断是否是在字典中
            std::string sub = s.substr(index, i - index);
            if (setDict.find(sub) != setDict.end()) {
                queue.push(i);
            }
        }
    }

    return false;
}

int main() {
    std::string s = "leetcode";
    std::vector<std::string> wordDict = {"leet", "code"};

    if (wordBreak(s, wordDict)) {
        std::cout << "The string can be segmented into words from the dictionary." << std::endl;
    } else {
        std::cout << "The string cannot be segmented into words from the dictionary." << std::endl;
    }

    return 0;
}
 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2129278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2024-09-12】某极验4流程分析-滑块验证码

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、流程分析三、参数分析三、代码一、前言 极验四代滑块没有了滑动轨迹的验证,来看一下 网址:aHR0cHM6Ly9n…

机器学习中的内存优化

随着机器学习模型的复杂性不断增加&#xff0c;内存使用量也随之增长&#xff0c;因此&#xff0c;内存优化变得尤为重要。 机器学习内存足迹 机器学习模型通常由数据结构如张量和矩阵组成。例如&#xff0c;一个形状为(1000, 1000)&#xff0c;每个元素为32位浮点数的二维张量…

TypeScript中 any和unknown 的区别

1、给其他变量赋值 any可以给其他类型的变量重新赋值&#xff1b; 但unknown是不行的&#xff0c;unknown 可以保持类型安全&#xff0c;从而减少潜在的错误&#xff1b; 2、使用类型上的方法 any可以用类型上的方法&#xff0c;unknown是不可以的

golang 字符串浅析

go的字符串是只读的 测试源代码 package mainimport ("fmt""unsafe" )func swap(x, y string) (string, string) {return y, x }func print_string(obj *string, msg string) {string_ptr : (*[2]uintptr)(unsafe.Pointer(obj))first_obj_addr : string_…

AI在医学领域:医学AI的安全与隐私全面概述

随着技术的进步&#xff0c;软件系统在商业产品中扮演着越来越重要的角色&#xff0c;并在医疗领域变得不可或缺。人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;的发展已经彻底改变了现代医疗系统&#xff0c;为通过病人诊断、监测和医疗保健研究收…

[机器学习]KNN算法

1 KNN算法简介 KNN算法思想&#xff1a;如果一个样本在特征空间中的K个最相似的样本中的大多数属于某一个类别&#xff0c;则该样本也属于这个类别。 K值过小&#xff1a;用较小领域中的训练实例进行预测。 容易受到异常点的影响K值的减小意味着整体模型变得复杂&#xff0c;容…

Tableau学习日记

Day1&#xff1a;Tableau简介、条形图与直方图 1.Tableau绘制条形图 1.1 条形图1&#xff1a;各地区酒店数量 1.2 条形图2&#xff1a;各地区酒店均价 1.3 堆积图&#xff1a;价格等级堆积图 2.Tableau绘制直方图 2.1创建评分直方图 Day2&#xff1a;Tableau简介、条形图与直…

c# resource en-US

这里主要是做中英文语言包切换的&#xff0c;非常简单

Java教程:SE进阶【十万字详解】(上)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

c++11新特性——endable_shared_from_this

文章目录 一.解决场景代码示例原因 二.解决办法代码 三.底层原理 一.解决场景 一个share_ptr管理的类&#xff0c;如果从类的函数里返回类对象&#xff08;this指针&#xff09;&#xff0c;导致share_ptr引用计数错误&#xff0c;析构时异常问题 代码示例 #include <mem…

最近试用了FunHPC-AI宝箱-ComfyUI-Plus,使用了dreamshaperXL全能模型,生成了几张国风图,效果真的让人惊叹!

最近试用了FunHPC-AI宝箱-ComfyUI-Plus&#xff0c;使用了dreamshaperXL全能模型&#xff0c;生成了几张国风图&#xff0c;效果真的让人惊叹&#xff01;&#x1f338; https://www.funhpc.com/#/ 通过简单的提示词&#xff0c;我就能轻松生成出充满古韵的图像&#xff1a;汉服…

linux查看外网ipv4地址

在Linux系统中&#xff0c;可以使用以下几种方法来查看外网IPv4地址&#xff0c;并确保强制使用IPv4。 前言 特别感谢浪浪云对本文的大力支持。浪浪云作为领先的云计算服务提供商&#xff0c;凭借其卓越的性能和可靠性&#xff0c;帮助无数企业和开发者实现了业务的快速部署和…

苹果宣布iOS 18正式版9月17日推送:支持27款iPhone升级

9月10日消息&#xff0c;在苹果秋季发布会结束后&#xff0c; 苹果宣布将于9月17日(下周二)推送iOS 18正式版系统。 苹果官网显示&#xff0c;iOS 18正式版将兼容第二代iPhone SE及之后的所有机型&#xff0c;加上刚发布的iPhone 16系列&#xff0c;共兼容27款iPhone。 iOS 18升…

为拖延症量身定制的AI工具,让Kimi做我的《每日信息整理助手》

AI不止对传统行业带来巨大的改变&#xff0c;对日常生活也便利了不少&#xff0c;现在这个时代获取信息的方式太简单了。 我们每天都会接受大量的信息&#xff0c;难免一天下来会忘记很多事情&#xff0c;有时候突然想起了一个点子&#xff0c;有时候突然有一件急事、一件待办事…

基于SpringBoot+Vue的校园失物招领系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的校园失物…

2024年黑龙江事业单位考试报名保姆级流程

黑龙江事业编考试报名须知 一、报名安排 1.报名时间&#xff1a;9月18日9&#xff1a;00-9月24日17&#xff1a;00 2.报名网址&#xff1a;黑龙江省事业单位公开招聘服务平台 二、报名操作流程 本次招考采取网上报名的方式。 1.网上报名。应聘人员可于2024年9月18日9&…

企业数字化转型、建设和升级面临的主要难题和解决之道(2)

用爱编程30年&#xff0c;倾心打造工业和智能智造软件研发平台SCIOT,用创新的方案、大幅的让利和极致的营销&#xff0c;致力于为10000家的中小企业实现数字化转型&#xff0c;打造数字化企业和智能工厂&#xff0c;点击上边蓝色字体&#xff0c;关注“AI智造AI编程”或文末扫码…

总结拓展九:SAP数据迁移(1)

第一节&#xff1a;数据迁移介绍 1、SAP上线前MM模块需要迁移的数据 1.1 静态数据&#xff1a;物料主数据、供应商主数据等&#xff1b; 1.2 业务数据&#xff1a;采购订单&#xff08;未收货&#xff09;、发票校验&#xff08;未校验&#xff09;、采购信息记录、货源清单…

如何使用python运行Flask开发框架并实现无公网IP远程访问

文章目录 1. 安装部署Flask2. 安装Cpolar内网穿透3. 配置Flask的web界面公网访问地址4. 公网远程访问Flask的web界面 本篇文章主要讲解如何在本地安装Flask&#xff0c;以及如何将其web界面发布到公网进行远程访问。 Flask是目前十分流行的web框架&#xff0c;采用Python编程语…

【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234

1. 力扣876&#xff1a;链表的中间节点 1.1 题目&#xff1a; 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,…