100道算法题,每天1道

389 阅读4分钟

问题一:岛屿数量

给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。 岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。

题库地址:www.nowcoder.com/practice/0c…

思路:

利用dfs进行遍历,当找到陆地之后,向这个位置的上下左右继续寻找,如果依然找到陆地,则将矩阵中该位置代表陆地的’1’修改为’0’。这样,在之后的遍历中则不会再次计算该处陆地。

BFS,DFS介绍,可以重点看一下

参考链接:developer.aliyun.com/article/756…

深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等

js实现

 * 判断岛屿数量
 * @param grid char字符型二维数组 
 * @return int整型
 */
function solve(grid) {
  let lands = 0
  for (let h = 0; h < grid.length; h++) {
    for (let w = 0; w < grid[0].length; w++) {
      if (grid[h][w] == 1) {
        lands++
        dfs(grid, h, w)
      }
    }
  }
  return lands
}

function dfs(grid, h, w) {
  grid[h][w] = 0
  if (h - 1 >= 0 && grid[h - 1][w] == 1) {
    dfs(grid, h - 1, w)
  }

  if (h + 1 < grid.length && grid[h + 1][w] == 1) {
    dfs(grid, h + 1, w)
  }

  if (w - 1 >= 0 && grid[h][w - 1] == 1) {
    dfs(grid, h, w - 1)
  }

  if (w + 1 < grid[0].length && grid[h][w + 1] == 1) {
    dfs(grid, h, w + 1)
  }
}

动态规划的解题思路

动态规划有点像初中归纳法,核心思想是拆分子问题,记住过往,减少重复计算。  并且动态规划一般都是自底向上,转换为计算机可识别的逻辑;

实际解决问题时,可以根据这个思路进行分析:

1、判断是否能用归纳法得到一个规律,

2、想办法记录每一次的结果,需要判断影响结果的变量,来用不同的方式进行记录

3、再自底向上进行循环处理,常见的循环都是先循环主要变量,在内部循环结果(或者循环影响结果的主要变量)进行判断和计算

动态规划有几个典型特征:最优子结构、状态转移方程、边界、重叠子问题

问题二:单词划分

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

  • 拆分时可以重复使用字典中的单词。
  • 你可以假设字典中没有重复的单词

示例

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

题库地址:leetcode.com/problems/wo…

思路: 动态规划,参考链接:zhuanlan.zhihu.com/p/365698607

f(1) = true(1)

f(2) = true(1) + true(2) / true(1-2)

f(3) = true(2) + true(3) / true(1) + true(2,3) / true(1,3)

f(4) = true(3) + true(4) / true(2) + true(3,4) / true(1) + true(2,4) / true(1,4)

f(n) = true(n-1) + true(n) / true(n-2) + true(n-2,n) / true(n-3) + true(n-3,n) /... / true(1,n)

上面已经归纳得到边界状态转移公式,开始思考如何存储上一次记录的结果;

记录每一次的结果状态:每一次的结果只可能为true或者是false,可用通一个字符串长度的数组来进行记录

从下向上循环:先循环主要变量字符串长度,再循环结果进行记录和判断

套入上方思路,进行代码实现

js实现:

/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
 var wordBreak = function(s, wordDict) {
  const wordSet = new Set(wordDict)
  // 用来记录结果,第一项填充true,将边界情况套入公式
  const dp = new Array(s.length + 1).fill(false)
  dp[0] = true
  for(let i=1;i<=s.length;i++) {
    for(let j=0;j<i;j++) {
      if(dp[j] && wordSet.has(s.substring(j, i))) {
        dp[i] = true
        break
      }
    }
  }
  return dp[s.length]
    
};

问题三:挖矿问题

10 个工人

5个金矿,每个金矿对应收益和所需工人如下

400kg/5人 500kg/5人 200kg/3人 300kg/4人 350kg/3人

每个金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿

求最佳金矿收益

参考链接:www.sunbohao.com/font/conten…

思路: 动态规划

设工人数量为 workersNum,金矿数量为 mineNum,金矿含量为数组 mineValArr,金矿开采所需的人数为数组 workersUsedArr

mineNum === 0,

输出 : 0;

mineNum === 1,

workersNum < workersUsedArr[0],输出0;

workersNum > workersUsedArr[0],输出mineValArr[0];

mineNum === 2,

workersNum < workersUsedArr[0], workersNum < workersUsedArr[0],输出0;

workersNum > workersUsedArr[0], workersNum < workersUsedArr[1],输出mineValArr[0];

workersNum < workersUsedArr[0], workersNum > workersUsedArr[1],输出mineValArr[1];

workersNum > workersUsedArr[0], workersNum > workersUsedArr[1],输出mineValArr[0] + mineValArr[1];


f(0) = 0 ; // (mineNum === 0)

f(1) = 0 ; // (mineNum === 1,workersNum < workersUsedArr[0])

f(1) = mineValArr[0] ; //(mineNum ===1, workersNum > workersUsedArr[0]

一个变量无法形成状态转移方程,将 workersNum代入变量


f(0,0) = 0 ;

f(1,workersNum) = workersNum > workersUsedArr[0] ?  mineValArr[0] ;0; 

f(2,workersNum) = workersNum > (workersUsedArr[0] + workersUsedArr[1]) ? mineValArr[0] + mineValArr[1] : (workersNum > workersUsedArr[1] ? Max(f(1,workersNum), f(1,workersNum) : f(1,workersNum))

公式计算中,workersUsedArr,mineValArr也参与了计算,同样带入变量用于判断


f(0,workersNum, mineValArr, workersUsedArr) = 0 ;

f(1,workersNum, mineValArr, workersUsedArr) = workersNum > workersUsedArr[0] ?  mineValArr[0]0; 

f(2,workersNum, mineValArr, workersUsedArr) = workersNum > (workersUsedArr[0] + workersUsedArr[1]) ? mineValArr[0] + mineValArr[1] : (workersNum > workersUsedArr[1] ? Max(f(1,workersNum), f(1,workersNum) : f(1,workersNum))

f(n,workersNum, mineValArr, workersUsedArr) = workersNum > (workersUsedArr[0] + ... + workersUsedArr[n-1]) ? mineValArr[n-2] + mineValArr[n-1] : (workersNum > workersUsedArr[n-1] ? Max(f(n-1,workersNum), f(n-2,workersNum) : f(n-2,workersNum))

上述思路可以用来递归,但是无法用来进行自底向上的循环,得想办法得到状态转移方程,考虑状态方程时只考虑可变量

// 这一步还没想到具体的思路,是根据参考链接中结果与结论得到的公式
F(mineNum, workersNum) = 0 (mineNum=0或workersNum=0)

// mineNum>=1,workersNum < workersUsedArr[mineNum-1]
F(mineNum, workersNum) = F(mineNum-1, workersNum)

// mineNum>=1,workersNum >= workersUsedArr[mineNum-1]
F(mineNum, workersNum) = max(F(mineNum-1, workersNum), F(mineNum-1, workersNum-workersUsedArr[mineNum-1]+ mineValArr[mineNum-1]))

上面已经归纳得到边界状态转移公式,开始思考如何存储上一次记录的结果;

记录每一次的结果状态:每一次的结果根据workersNum数量的不同,结果不同,没法用一个一维数组进行存储,考虑二维数组是否可以存储结果,用mineNum主要变量作为行数,用workersNum作为列数,可以记录每个mineNum对应的所有可能;

从下向上循环:先循环主要变量字符串长度,再循环影响结果的主要变量workersNum,进行记录和判断

套入上方思路,进行代码实现

js实现:

function f(mineNum, workersNum, mineValArr, workersUsedArr) {
  /**
   * 用mineNum主要变量作为行数,用workersNum作为列数,可以记录每个mineNum对应的所有可能
   * 通过对结果最上一行和最左一列填充0,来减少对边界的判断
   */

  let resultArr = new Array(mineNum + 1)

  for (let i = 0; i < resultArr.length; i++) {
    resultArr[i] = new Array(workersNum + 1).fill(0)
  }

  /**
   * 先循环主要变量字符串长度,再循环结果进行记录和判断
   */
  for (let i = 1; i <= mineNum; i++) {
    for (let j = 1; j <= workersNum; j++) {
      if (j < workersUsedArr[i - 1]) {
        resultArr[i][j] = resultArr[i - 1][j]
      } else {
        resultArr[i][j] = Math.max(resultArr[i - 1][j], resultArr[i - 1][j - workersUsedArr[i - 1]] + mineValArr[i - 1])
      }
    }
  }

  return resultArr[mineNum][workersNum]
}

问题四:NC145 01背包

问题链接:www.nowcoder.com/practice/28…

描述

image.png

示例1

输入:10,2,[[1,3],[10,4]]
返回值:4
第一个物品的体积为1,重量为3,第二个物品的体积为10,重量为4。只取第二个物品可以达到最优方案,取物重量为4   

备注:

image.png

思路: 这题目看变量与判断条件,和挖矿问题几乎没有什么区别,都是一种总量,一个限制变量,一个价值变量,感觉这种题已经是固定套路,做一个二维数组存放所有可能的情况,然后可能的结果为:

问题五、最大子序和

问题链接:leetcode-cn.com/problems/ma…

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 :

输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。 示例 2:

输入:nums = [1] 输出:1

思路1

求连续子序列最大和,比较当前值是否大于(当前值+前面的子序列之和),若是,则从当前位置重新开始累加

var maxSubArray = function (nums) {
  if (!nums || nums.length < 1) {
    return 0
  }
  let maxVal = nums[0]
  let currentVal = nums[0]
  for (let i = 1; i < nums.length; i++) {
    currentVal = Math.max(nums[i], currentVal + nums[i])
    maxVal = Math.max(currentVal, maxVal)
  }
  return maxVal
};