目录
广度优先遍历可以解决:无权图的最短路径等问题
以下的三道题,没有直接给一个图进行遍历,因此我们需要对题目进行分析,从而转化为图的思想去进行bfs或dfs
279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n =12 输出: 3 解释: 12 = 4 + 4 + 4.示例 2:
输入: n =13 输出: 2 解释: 13 = 4 + 9.
对于这道题,可能第一直觉会是贪心算法,但是举个栗子。
(1)12=9+1+1+1 (2)12=4+4+4 明显2的解法更为准确
因此需要对该问题进行建模,将整个问题转化为一个图论的问题
(1)从n到0,每个数字表示一个节点
(2)如果两个数字x到y相差了一个完全平方数,则连接一条边
(3)则可以得到一个无权图,原问题转为求这个无权图中从n到0的最短路径
如下图:构建图的过程
(1)4到3相差1,3到2相差1,2到1相差1,1到1相差1 4到0相差4,为2的平方数
(2)依次类推,得到第二张图9到0相差3的平方数,9到5相差2的平方数,9到8相差1的平方数
class Solution {
function numSquares($n) {
//bfs,广度优先遍历
$queue = []; //初始化队列
$queue[] = [$n,0]; //[数字,步数]
$visited = []; //记录已经访问的节点,防止下面多次访问
$visited[$n] = true;
while (!empty($queue)) {
$node = array_shift($queue); //依次出队进行分析
$num = $node[0]; //数字
$step = $node[1]; //步数
for($i = 1; ;$i++){
$res = $num-$i*$i; //得出相差弹出元素某个平方数的结果
if($res < 0){ break; } //若该结果小于0,则已超出范围
if($res == 0){ return $step+1; }//等于0,则得到最终答案
if(empty($visited[$res])){ //如果该结果已经被访问到,则表示到达该结果可以有更短的路径
$queue[] = [$res,$step+1]; //如果该结果还没有被访问到,则进行入队
$visited[$res] = true; //记录该结果被访问到
}
}
}
}
}
127.单词接龙
给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: 5 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。示例 2:
输入: beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] 输出: 0 解释: endWord "cog" 不在字典中,所以无法进行转换。
对该问题进行建模,将整个问题转化为一个图论的问题
(1)每个单词表示一个节点
(2)如果两个单词相差了一个字母,则连接一条边
(3)则可以得到一个无权图,原问题转为求这个无权图中从beginWord到endWord的最短路径
解法:
(1)初始化:初始化条件判断,初始化构建图,构建队列,构建距离结果数组
(2)依次出队,进行广度遍历,将访问过的节点记录到距离结果数组中,并入队
(3)循环进行,直到找到endWord,返回最终答案
class Solution {
function ladderLength($beginWord, $endWord, $wordList) {
if(!in_array($endWord,$wordList)) return 0; //如果字典中没有结束单词,则永远也找不到结果,返回0
$graph = $this->constructGraph($beginWord,$wordList,$graph); //构建图
$distance = []; //记录到某个单词的最短距离,也可当做记录某节点是否被访问
$distance[$beginWord] = 1; //初始化开始单词的距离
$queue= [$beginWord]; //初始化队列
while (!empty($queue)) {
$curWord = array_shift($queue); //依次出队进行分析
foreach ($graph[$curWord] as $v) { //只分析与curWord相差1位的单词
if($v == $endWord) return $distance[$curWord]+1; //如果已经找到了endWord,则程序结束,返回结果
if(!isset($distance[$v])){ //判断是否已经记录了某单词,没有则进行后续操作
$distance[$v] = $distance[$curWord]+1; //该单词是curWord下一步的操作
$queue[] = $v; //入队
}
}
}
return 0;
}
/**
* 初始化构建邻接矩阵,图,以便进行后续的广度遍历
*/
public function constructGraph($beginWord,$wordList){
if(!in_array($beginWord,$wordList))
$wordList[] = $beginWord;
$graph = [];
$len = count($wordList);
//依次取出进行比较,压入结果图中
for($i=0;$i<$len;$i++){
for($j=$i+1;$j<$len;$j++){
if($this->compareWord($wordList[$i],$wordList[$j])){
$graph[$wordList[$i]][] = $wordList[$j];
$graph[$wordList[$j]][] = $wordList[$i];
}
}
}
return $graph;
}
/**
* 比较两个单词是否相差一位
*/
public function compareWord($a,$b){
$len = strlen($a); //题目规定单词长度一直,因此只需得一个单词的长度即可
$different = 0; //比较过程,每一个字母进行比较
for($i = 0;$i<$len;++$i){
if($a[$i] != $b[$i]){
$different++;
}
}
return $different == 1; //返回是否相差1个字母
}
}
126.单词接龙 II
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: [ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]示例 2:
输入: beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] 输出: [] 解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
与127的区别,127只需得出最短的长度,126需要保存所有最短的序列,所以需要用其他方式或数据结构保存结果序列
因此,构建图的过程同126一样
解法:bfs+dfs
(1)初始化条件判断,endWord是否在结果数组中
(2)构建无权图-邻接矩阵
(3)进行广度优先遍历bfs,得到距离记录数组
(4)进行深度优先遍历dfs,得到最终结果数组
class Solution {
function findLadders($beginWord, $endWord, $wordList) {
if(!in_array($endWord,$wordList)) return []; //如果字典中没有结束单词,则永远也找不到结果,返回空数组
$graph = $this->constructGraph($beginWord,$wordList); //构建图
$distance = $this->bfs($beginWord,$endWord,$graph); //进行广度优先遍历获得距离结果数组(从beginWord到endWord的距离记录数组)
$res = []; //初始化最终返回结果数组
$this->dfs($beginWord,$endWord,$graph,$distance,[$beginWord],$res); //深度优先遍历
return $res;
}
/**
* 初始化构建邻接矩阵,图,以便进行后续的广度遍历
*/
public function constructGraph($beginWord,$wordList){
if(!in_array($beginWord,$wordList))
$wordList[] = $beginWord;
$graph = [];
$len = count($wordList);
//依次取出进行比较,压入结果图中
for($i=0;$i<$len;$i++){
for($j=$i+1;$j<$len;$j++){
if($this->compareWord($wordList[$i],$wordList[$j])){
$graph[$wordList[$i]][] = $wordList[$j];
$graph[$wordList[$j]][] = $wordList[$i];
}
}
}
return $graph;
}
/**
* 广度优先遍历获得距离记录数组
*/
public function bfs($beginWord,$endWord,$graph){
$queue = [$beginWord];
$distance = []; //记录到某个单词的最短距离,也可当做记录某节点是否被访问
$distance[$beginWord] = 0; //初始化开始单词的距离
while (!empty($queue)) {
$curWord = array_shift($queue); //依次出队进行分析
foreach ($graph[$curWord] as $v) { //只分析与curWord相差1位的单词
if(!isset($distance[$v])){ //判断是否已经记录了某单词,没有则进行后续操作
$distance[$v] = $distance[$curWord]+1; //该单词是curWord下一步的操作
if($v == $endWord) return $distance; //如果已经访问到endWord,则可直接返回距离记录数组
$queue[] = $v; //入队
}
}
}
return $distance;
}
/**
* 深度优先遍历获得最终返回数组
* $res采用引用调用,方便进行赋值,同时也优化代码,无需返回数组
*/
public function dfs($curWord,$endWord,$graph,$distance,$path,&$res){
if($curWord == $endWord){
$res[] = $path; //当找到endWord时,即可以将整个path压入结果数组中
}else{
foreach ($graph[$curWord] as $v) { //只分析与curWord相差1位的单词
if($distance[$v] == $distance[$curWord] + 1){ //判断该单词是否是curWord的下一步单词
$path[] = $v; //是则将该单词压入path中
$this->dfs($v,$endWord,$graph,$distance,$path,$res); //继续深度遍历
array_pop($path); //弹出压入的单词,以便后序压入新的path
}
}
}
}
/**
* 比较两个单词是否相差一位
*/
public function compareWord($a,$b){
$len = strlen($a); //题目规定单词长度一直,因此只需得一个单词的长度即可
$different = 0; //比较过程,每一个字母进行比较
for($i = 0;$i<$len;++$i){
if($a[$i] != $b[$i]){
$different++;
}
}
return $different == 1; //返回是否相差1个字母
}
}