代码随想录算法训练营第三十七天 | 738. 单调递增的数字、714. 买卖股票的最佳时机含手续费、968. 监控二叉树

59 阅读3分钟

738. 单调递增的数字

链接

文章链接

题目链接

第一想法

这道题的想法是从前往后找,直到找到一个不满足arr[i]>=arr[i-1]的元素下标,之后往前推,判断arr[i-1]-1后和arr[i-2]的大小,保证修改后前面的还满足单调递增的条件,不满足就继续向前寻找,直到最终找到或者到下标为0的元素截止,之后把目标元素-1,之后的元素都设置为9就可以了,代码如下:

function monotoneIncreasingDigits(n: number): number {
  let arr: string[] = String(n).split('')//转化成数组利于修改元素
  let index: number = arr.length
  for (let i = 1; i < arr.length; i++) {
    if (i > index) {//index如果被修改过就说明前面已经修改成递增了,只需要把后面的元素都变成9就可以了
      arr[i] = '9'
      continue
    }
    if (arr[i] >= arr[i - 1]) continue
    index = i //找到了不满足递增条件元素的下标
    while (index > 0 && Number(arr[index]) - 1 < Number(arr[index - 1])) {//往回找
      arr[index] = '9'//设置为9  因为前面的某个元素要被修改成 元素-1 所以当前的设置为9就可以了
      index--
    }
    arr[index] = String(Number(arr[index]) - 1) //找到了要被修改的元素(修改后前面保证是递增的)
  }
  return Number(arr.join(''))//返回结果
}

看完文章的想法

我的想法是从前往后,文章的想法是从后往前,因为只需要找到第一个元素小于前面的元素就可以了,之后把后面的元素都赋值给9就可以了

function monotoneIncreasingDigits(n: number): number {
  let arr: string[] = String(n).split('')//排序 为了好修改值
  let index: number = arr.length //标记  从哪里开始之后后面的元素都设置为9
  for (let i = arr.length - 1; i > 0; i--) {
    if (arr[i - 1] > arr[i]) { //当前元素小于前一个元素 则更新标记index,同时把前一个元素减一
      index = i
      arr[i - 1] = String(Number(arr[i - 1]) - 1)
    }
  }
  for (let i = index; i < arr.length; i++) {//从标记元素开始每个元素都设置为9
    arr[i] = '9'
  }
  return Number(arr.join('')) //返回结果
}

思考

这道题想了一会就想出来了,没想到自己可以想出来,但是还是有点缺陷,从后向前更容易理解,从前向后可能没这么容易理解,但是总的来说,能想出来就是好事。

714. 买卖股票的最佳时机含手续费

链接

文章链接

题目链接

第一想法

我的想法是通过记录上一个坡底+上一个坡底到坡顶的差值-fee,记录当前坡底到坡顶的差值-fee,与上一个坡底到坡顶的差值加和后比较前一个坡底到当前坡顶的差值-fee的大小,之后根据大小来改变记录的坡底和差值,直到最后。但是想了半天发现代码怎么写都写不对。写了40分钟后放弃了决定看看文章怎么写

看完文章的想法

这道题用动态规划比较好,当然用贪心也能做,而且文章的思路也比我的清晰很多,这道题的思路是找到低价格买入,高价格卖出,有时候卖出的价格可能不是最高的(也就是说不是收获利润区间的最后一天),所以卖出时重新给价格赋值为当前价格减去手续费(原因是让只有收获利润的最后一天才减去手续费)代码如下:

function maxProfit(prices: number[], fee: number): number {
  let res: number = 0
  let minPrice: number = prices[0]
  for (let i = 1; i < prices.length; i++) {
    if (minPrice > prices[i]) {
      minPrice = prices[i] //确定买入价格为低价格
    }
    if (minPrice + fee < prices[i]) { //卖出
      res += prices[i] - minPrice - fee //这里的授予要减去手续费
      minPrice = prices[i] - fee//这里-fee就是核心所在,这里模拟的时如果是获利区间的一天则相当于没有卖出 直到是获利区间的最后一天才卖出
    }
  }
  return res
}

思考

这道题确实没想到,即使看完文章的讲解也是云里雾里的,这道题还是得多琢磨,贪心算法确实不好想,看来得多加练习才能掌握其中的真谛。

968. 监控二叉树

链接

文章链接

题目链接

第一想法

这道题我的想法是肯定是找到节点且节点四周围是没有监控的,所以它应该是一个被监控的,但是不知道怎么写,所以自己尝试了一个简单的写法,就是通过二叉树的层数来决定是奇数层安装监控还是偶数层安装监控,不出意外,运行不通过,下面的代码就当记录了:

//以下代码是不能通过测试的,这里就当记录了
function minCameraCover(root: TreeNode | null): number {
  if (root!.left == null && root!.right == null) return 1
  let odd: number = 0
  let even: number = 0
  let index: number = 0
  let arr: (TreeNode | null)[] = [root]
  while (arr.length > 0) {
    index++
    let sum: number = arr.length
    if (index % 2 == 0) {
      even += sum
    } else odd += sum
    while (sum--) {
      let node: TreeNode = arr.pop()!
      if (node.left) arr.push(node.left)
      if (node.right) arr.push(node.right)
    }
  }
  return index % 2 == 0 ? odd : even
}

看完文章后的想法

看完文章后,发现我还是需要继续磨练,我的第一想法是想复杂了,但是也要一层一层的看,但是要从叶子节点到根节点看,因为叶子节点是不能放摄像的,放摄像就不是最少的了,同时用三种状态标记节点的状态:

 0:当前节点没有被覆盖
 1: 当前节点安装摄像头
 2: 当前节点已经被覆盖

通过这三种状态来判断是非需要安装摄像头,同时要利用后序遍历来解决这个问题,详细解释在代码中展示

function minCameraCover(root: TreeNode | null): number {
  let res: number = 0 //存储结果值
  const foo: (root: TreeNode | null) => number = (root) => {
    if (root == null) return 2 //如果节点为null的话 要设置为2表示已覆盖 不能是0和1 如果是0的话叶子节点会被安装摄像头 这是我们不想看到的 如果是1的话,那么叶子节点的爷爷节点才会被安装摄像头,这样这棵树就不是被完全监控的
    let left: number = foo(root.left)
    let right: number = foo(root.right)
    if (left == 2 && right == 2) return 0 //如果左右孩子都已经被覆盖了 则返回0表示当前节点未被覆盖
    /*
    只要左右孩子有一个孩子未被覆盖 则当前节点一定要设置为摄像头
    left==0&&right==1 
    left==1&&right==0
    left==0&&right==2
    left==2&&right==0
    left==0&&right==0
    */
    if (left == 0 || right == 0) { //这个一定要在 left==1||right==1条件之前
      res++
      return 1
    }
    /*
    如果左右孩子有一个有摄像头同时另一个孩子不是未覆盖的 则当前节点设置为被覆盖的(2)
    left==1&&right==1 
    left==1&&right==2 
    left==2&&right==1 
    */
    if (left == 1 || right == 1) return 2
    return -1 //这里是防止代码提示 实际上是走不到这里的
  }
  if (foo(root) == 0) res++ //这个是判断根节点是否需要安装摄像头
  return res
}

思考

这道题难,确实没想到,不愧是hard。主要是没想到要从叶子节点看,因为叶子节点是指数级增长的,而根节点只有一个,所以要保证叶子节点不要安装摄像头,通过也要想清三种状态:未被覆盖,被覆盖,安摄像头三种所对应的不同情况。同时也代码是也要主要三种情况的顺序

今日总结

今天的三道题,总共做出一道题,也就是第一题比较简单。第二三题都没想到,果然贪心算法的水还是很深的,今日耗时3小时