JS算法之旋转数组的最小数字及矩阵中的路径

458 阅读2分钟

这是我参与8月更文挑战的第19天,活动详情查看:8月更文挑战

旋转数组的最小数字

剑指Offer 11.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组【3,4,5,1,2】为【1,2,3,4,5】的一个旋转,该数组的最小值为1。

示例1:

 输入:[3,4,5,1,2]
 输出:1

示例2:

 输入:[2,2,2,0,1]
 输出:0

题解

暴力法

根据题意,我们第一时间就能通过暴力解决,用一个变量记录一下当前遍历过程中遇到的最小值是多少,然后遍历结束后,返回最小值即可。

但这样子的暴力不够好,因为其实数组是被旋转的,所以,在遍历过程中,只要有小于numbers[0],那么该数字一定是最小值。

 function minArray(numbers){
   for(let i=0;i<numbers.length;i++){
     if(numbers[i]<numbers[0]){
       return numbers[i]
     }
   }
   return numbers[0]
 }

但暴力不是我们的目标,我们目的应该是降低算法的复杂度,所以采用二分法更好。

二分法

首先,创建两个指针left、right分别指向numbers首尾数字,然后计算出两指针之间的中间索引值middle,然后我们会遇到以下三种情况:

  1. middle > right:代表最小值一定在middle右侧,所以left移到middle+1的位置。
  2. middle < right:代表最小值一定在middle左侧或者就是middle,所以right移到middle到位置。
  3. middle既不大于left指针,也不小于right指针的值,代表着middle可能等于left指针的值,或者right指针的值,我们这时候只能让right指针递减,来一个一个找最小值了。
 /**
  * @param {number[]} numbers
  * @return {number}
  */
 var minArray = function(numbers){
   let left = 0, right = numbers.length-1;
   while(left < right){
     let middle = left + ~~((right - left) / 2); // 避免(left+right)出现溢出
     if(numbers[middle] > numbers[right]){
       left = middle + 1;
     }else if(numbers[middle] < numbers[right]){
       right = middle;
     }else{
       right--;
     }
   }
   return numbers[left];
 }

矩阵中的路径

剑指Offer 12.矩阵中的路径

给定一个m x n二维字符网格board和一个字符串单词word。如果word存在于网格中,返回true;否则,返回false。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

例如,在下面的3x4的矩阵中包含单词“ABCCED”(单词中的字母已标出)。

img

示例1:

 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
 输出:true

示例2:

 输入:board = [["a","b"],["c","d"]], word = "abcd"
 输出:false

提示:

  • 1 <= board.length <= 200
  • 1 <= board[i].length <= 200
  • board 和 word 仅由大小写英文字母组成

题解

典型的矩阵搜索问题,可使用深度优先搜索(DFS)+ 剪枝解决。

  • 深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS通过递归,先朝一个方向搜索到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
  • 剪枝: 在搜索中,遇到这条路不可能和目标字符串匹配成功的情况(例如:此矩阵元素和目标字符不同、此元素已被访问),则应立即返回,称之为可行性剪枝

Picture0.png

DFS解析:

  • 递归参数:当前元素在矩阵board中的行列索引 x 和 y,当前目标字符在word中的索引 k。

  • 终止条件:

    1. 返回false:

      • 行或列索引越界
      • 当前矩阵元素与目标字符不同
      • 当前矩阵元素已被访问
    2. 返回true:

      • k === word.length - 1,即字符串word已全部匹配。
  • 递推工作:

    1. 标记当前矩阵元素:将board[ x ] [ y ] 修改为字符串 ‘ - ’,代表此元素已访问过,防止之后搜索时重复访问。
    2. 搜索下一单元格:朝当前元素的 上、右、下、左 四个方向开启下层递归,使用 连接(代表只需找到一条可行路径就直接返回,不再做后续DFS),并记录结果至 res。
    3. 还原当前矩阵元素:将board [ i ] [ j ]元素还原至初始值
 /**
  * @param {character[][]} board
  * @param {string} word
  * @return {boolean}
  */
 var exist = function(board, word){
   var xLen = board.length;
   var yLen = board[0].length;
   var k = 0;
   
   var dfs = function(x,y,k){
     if(x < 0 || x >= xLen || y < 0 || y >= yLen || board[x][y] !== word[k])
       return false;
     if(k === word.length - 1) // word 遍历完了
       return true;
     let temp = board[x][y]; // 记录board的值
     board[x][y] = '-';      // 
     let res = dfs(x - 1, y, k + 1) || dfs(x, y + 1, k + 1) || dfs(x + 1, y,  k + 1) || dfs(x, y - 1,  k + 1); // 上 右 下 左
     board[x][y] = temp;
     return res;
   }
   
     for(let i = 0; i < xLen; i++){
       for(let j = 0; j < yLen; j++){
         if(dfs(i,j,k)) return true;
       }
     }
   return false;
 }

坚持每日一练!前端小萌新一枚,希望能点个哇~