《剑指 Offer I》刷题笔记 11 ~ 19 题

  • 查找算法(中等)
    • 11. 二维数组中的查找
      • _解法 1:暴力迭代
      • 解法 2:标志数
      • 解法 3:逐行二分
    • 12. 旋转数组的最小数字
      • _解法 1:暴力迭代
      • 解法 2:二分
    • 13. 第一个只出现一次的字符
      • _解法 1:暴力迭代
      • _解法 2:哈希
      • 解法 3:有序哈希表
  • 搜索与回溯算法(简单)
    • 14. 从上到下打印二叉树 I
      • 解法 1:队列
    • 15. 从上到下打印二叉树 II
      • 解法 1:队列
      • 解法 2:递归
    • 16. 从上到下打印二叉树 III
      • _解法 1:双栈
      • 解法 2:双端队列
      • 解法 3:层序遍历 + 倒序
      • Go 语言 切片
    • 17. 树的子结构
      • 解法 1:双重递归 DFS
      • 解法 2:双重 BFS(很慢)
    • 18.二叉树的镜像
      • _解法 1:递归 DFS
      • 解法 2:辅助栈
    • 19. 对称的二叉树
      • _解法 1:暴力
      • 解法 2:递归

小标题以 _ 开头的题目和解法代表独立想到思路及编码完成,其他是对题解的学习。

VsCode 搭建的 Java 环境中 sourcePath 配置比较麻烦,可以用 java main.java 运行(JDK 11 以后)

Go 的数据结构:LeetCode 支持 https://godoc.org/github.com/emirpasic/gods 第三方库。

go get github.com/emirpasic/gods

查找算法(中等)

11. 二维数组中的查找

题目:剑指 Offer 04. 二维数组中的查找

_解法 1:暴力迭代

时间复杂度:O(n * m)

// go
func findNumberIn2DArray(matrix [][]int, target int) bool {for _, item := range matrix {for _, v := range item {if target == v {return true}}}return false
}

解法 2:标志数

题解:面试题04. 二维数组中的查找(标志数,清晰图解)

以后遇到二维数组,不一定要从 0, 0 开始循环,多点别的思路。。。

时间复杂度:O(n + m)

// go
func findNumberIn2DArray(matrix [][]int, target int) bool { row := len(matrix) // []   if row == 0 {     return false    }   col := len(matrix[0])  // [[]] if col == 0 {     return false    }   i, j := row-1, 0   for i >= 0 && j < col {      if matrix[i][j] > target {           i--     } else if matrix[i][j] < target {            j++       } else {            return true     }   }   return false
}

解法 3:逐行二分

还是没有领悟那句话的精髓 ---- 遇到有序数组就用二分

思路:依旧是遍历每行,对行再遍历的时候使用二分查找

时间复杂度:O(n * log(m))

// java
class Solution {public boolean findNumberIn2DArray(int[][] matrix, int target) {if (matrix.length == 0) {return false;}for (int i = 0; i < matrix.length; i++) {int index = Arrays.binarySearch(matrix[i], target);if (index >= 0) {return true;}}return false;}
}

12. 旋转数组的最小数字

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

_解法 1:暴力迭代

思路:遍历找到第一个比左边的数字大的数字,就是目标值

func minArray(numbers []int) int {   if len(numbers) == 1 {        return numbers[0]}  for i := 1; i < len(numbers); i++ {       if numbers[i] < numbers[i-1] {           return numbers[i]       }   }   return numbers[0]
}

题解中看到的思路:遍历找到比 numbers[0] 小的值就是目标值

func minArray(numbers []int) int {   for i := 0; i < len(numbers); i++ {       if numbers[i] < numbers[0] {         return numbers[i]       }   }   return numbers[0]
}

解法 2:二分

二分的模板已经很熟了,但是 真正应用还需要多练!

核心在于在适当的时候控制边界条件!

func minArray(number []int) int {left, right := 0, len(number)-1for left < right {mid := left + (right-left)>>1if number[mid] < number[right] {right = mid} else if number[mid] > number[right] {left = mid + 1} else {right--}}return number[left]
}
func minArray(numbers []int) int {left, right := 0, len(numbers)-1for left < right {mid := left + (right-left)>>1if numbers[mid] < numbers[right] {right--} else if numbers[mid] > numbers[right] {left = mid + 1}}return numbers[left]
}

13. 第一个只出现一次的字符

题目:剑指 Offer 50. 第一个只出现一次的字符

_解法 1:暴力迭代

若从前往后找到某个元素的索引,和从后往前找到某个元素的索引相同,则说明只出现一次。

好像由于下面的方法每次要执行 s[i],还是会比上面慢一点。

// go
// 转成 byte 数组后遍历
func firstUniqChar(s string) byte { b := []byte(s) for _, v := range b {      if bytes.LastIndexByte(b, v) == bytes.IndexByte(b, v) {           return v        }   }   return ' '
}
// 直接遍历字符串
func firstUniqChar(s string) byte { for i := 0; i < len(s); i++ {     if strings.IndexByte(s, s[i]) == strings.LastIndexByte(s, s[i]) {         return s[i]     }   }   return ' '
}

_解法 2:哈希

// go
func firstUniqChar2(s string) byte {m := make(map[byte]int, 0)b := []byte(s)for _, v := range b {m[v]++}for _, v := range b {if m[v] == 1 {return v}}return ' '
}

评论中的做法:是哈希的思路,但是用数组存储。

对于这种存储范围已经固定的,完全可以用数组。

// go
func firstUniqChar3(s string) byte {if s == "" {return ' '}dic := make([]int, 26)b := []byte(s)for _, v := range b {dic[v-'a']++}for _, v := range b {if dic[v-'a'] == 1 {return v}}return ' '
}

解法 3:有序哈希表

题解:面试题50. 第一个只出现一次的字符(哈希表 / 有序哈希表,清晰图解)

该思路我是想到了,不过 Go 中没有 有序哈希表 这个数据结构,Java 中有 LinkedHashMap。

// java
public char firstUniqChar2(String s) {Map<Character, Boolean> dic = new LinkedHashMap<>();char[] sc = s.toCharArray();for (char c : sc)dic.put(c, !dic.containsKey(c));for (Map.Entry<Character, Boolean> d : dic.entrySet()) {if (d.getValue())return d.getKey();}return ' ';
}

搜索与回溯算法(简单)

14. 从上到下打印二叉树 I

题目:面试题32 - I. 从上到下打印二叉树

二叉树的层序遍历,又称二叉树的 广度优先搜索(BFS)。

广度优先搜索算法(Breadth-First-Search,缩写为 BFS),是一种利用 队列 实现的搜索算法。

简单来说,其搜索过程和 “湖面丢进一块石头激起层层涟漪” 类似。

深度优先搜索算法(Depth-First-Search,缩写为 DFS),是一种利用 递归 实现的搜索算法。

简单来说,其搜索过程和 “不撞南墙不回头” 类似。

解法 1:队列

// java
class Solution {public int[] levelOrder(TreeNode root) {if (root == null) return null;List<Integer> resList = new ArrayList<>();Queue<TreeNode> queue = new LinkedList<>() {{ offer(root); }};while (!queue.isEmpty()) {TreeNode node = queue.poll();resList.add(node.val);if (node.left != null)queue.offer(node.left);if (node.right != null)queue.offer(node.right);}// list ---> int[]return resList.stream().mapToInt(e -> e.intValue()).toArray();// int[] resArray = new int[resList.size()];// for (int i = 0; i < resList.size(); i++) // resArray[i] = resList.get(i);// return resArray;}
}
// go
func levelOrder(root *TreeNode) []int { if root == nil {      return []int{}  }   var res = make([]int, 0)   queue := make([]*TreeNode, 0)  queue = append(queue, root)    for len(queue) != 0 {      node := queue[0]       queue = queue[1:] // 模拟队列出队        res = append(res, node.Val)        if node.Left != nil {          queue = append(queue, node.Left)       }       if node.Right != nil {         queue = append(queue, node.Right)      }   }   return res
}

15. 从上到下打印二叉树 II

题目:剑指 Offer 32 - II. 从上到下打印二叉树 II

解法 1:队列

题解:面试题32 - II. 从上到下打印二叉树 II(层序遍历 BFS,清晰图解)

class Solution {public List<List<Integer>> levelOrder1(TreeNode root) {if (root == null) return new ArrayList<>();List<List<Integer>> resList = new LinkedList<>();Queue<TreeNode> queue = new LinkedList<>() {{ offer(root); }};while (!queue.isEmpty()) {List<Integer> tmpList = new ArrayList<>();// 取出一行元素for (int i = queue.size(); i > 0; i--) {TreeNode node = queue.poll();tmpList.add(node.val);if (node.left != null)queue.offer(node.left);if (node.right != null)queue.offer(node.right);}resList.add(tmpList);}return resList;}
}

解法 2:递归

这个解法是比较妙的,本质上是个先序遍历,

  • 遍历到哪一层就将这一层的 list 创建好,填入第一个元素
  • 当后面再次遍历来到这一层时,利用二维 list 的下标将结果放到对应的层中
class Solution {List<List<Integer>> resList = new ArrayList<>();public List<List<Integer>> levelOrder(TreeNode root) {helper(root, 0);return resList;}// 本质上是先序遍历public void helper(TreeNode node, int level) {if (node == null) return;if (resList.size() <= level)resList.add(new ArrayList<>());resList.get(level).add(node.val);helper(node.left, level + 1);helper(node.right, level + 1);}
}

16. 从上到下打印二叉树 III

题目:剑指 Offer 32 - III. 从上到下打印二叉树 III

_解法 1:双栈

思路:二叉树层次遍历的大模板下,加一些额外操作而已。

// java
public List<List<Integer>> levelOrder(TreeNode root) {if (root == null) return new ArrayList<>();List<List<Integer>> resList = new ArrayList<>();Stack<TreeNode> stack1 = new Stack<>();Stack<TreeNode> stack2 = new Stack<>();stack1.add(root);boolean flag = true;while (!(stack1.isEmpty() && stack2.isEmpty())) {List<Integer> tmpList = new ArrayList<>();if (flag) {for (int i = stack1.size(); i > 0; i--) {TreeNode node = stack1.pop();tmpList.add(node.val);if (node.left != null)stack2.add(node.left);if (node.right != null)stack2.add(node.right);}} else {for (int i = stack2.size(); i > 0; i--) {TreeNode node = stack2.pop();tmpList.add(node.val);if (node.right != null)stack1.add(node.right);if (node.left != null)stack1.add(node.left);}}resList.add(tmpList);flag = !flag;}return resList;
}

解法 2:双端队列

题解:面试题32 - III. 从上到下打印二叉树 III(层序遍历 BFS / 双端队列,清晰图解

// java
public List<List<Integer>> levelOrder(TreeNode root) {if (root == null) return new ArrayList<>();List<List<Integer>> resList = new ArrayList<>();Deque<TreeNode> deque = new LinkedList<>();deque.add(root);while (!deque.isEmpty()) {List<Integer> tmpList = new ArrayList<>();// 打印奇数层for (int i = deque.size(); i > 0; i--) {// 左 -> 右TreeNode node = deque.removeFirst();tmpList.add(node.val);// 先左后右加入下层节点if (node.left != null)deque.addLast(node.left);if (node.right != null)deque.addLast(node.right);}resList.add(tmpList);if (deque.isEmpty()) break;// 打印偶数层tmpList = new ArrayList<>();for (int i = deque.size(); i > 0; i--) {// 右 -> 左TreeNode node = deque.removeLast();tmpList.add(node.val);// 先右后左加入下层节点if (node.right != null)deque.addFirst(node.right);if (node.left != null)deque.addFirst(node.left);}resList.add(tmpList);}return resList;
}

解法 3:层序遍历 + 倒序

思路:在层序遍历的基础上,添加时将奇数层倒序。

public List<List<Integer>> levelOrder(TreeNode root) {  if (root == null) return new ArrayList<>();  List<List<Integer>> resList = new ArrayList<>();  Queue<TreeNode> queue = new LinkedList<>();  queue.add(root);  while (!queue.isEmpty()) {    List<Integer> tmpList = new ArrayList<>();    for (int i = queue.size(); i > 0; i--) {      TreeNode node = queue.poll();      tmpList.add(node.val);      if (node.left != null)        queue.offer(node.left);      if (node.right != null)        queue.offer(node.right);    }    // 奇数层倒序    if (resList.size() % 2== 1)      Collections.reverse(tmpList);    resList.add(tmpList);  }  return resList;}

Go 语言 切片

Go 语言模拟队列的先进先出,相当于取出头部一个值后,丢弃这个值 queue = queue[1:]

func levelOrder(root *TreeNode) [][]int {var res = make([][]int, 0)if root == nil {return res}queue := []*TreeNode{root}flag := false // 奇偶顺序控制for len(queue) > 0 {length := len(queue)tmp := make([]int, length)for i := 0; i < length; i++ {node := queue[0]queue = queue[1:]if node.Left != nil {queue = append(queue, node.Left)}if node.Right != nil {queue = append(queue, node.Right)}if flag {tmp[length-i-1] = node.Val} else {tmp[i] = node.Val}}res = append(res, tmp)flag = !flag}return res
}

17. 树的子结构

题目:剑指 Offer 26. 树的子结构

解法 1:双重递归 DFS

我的 helper 函数基本写对了,不过 isSubStructure 没考虑到用递归。。

class Solution {public boolean isSubStructure(TreeNode A, TreeNode B) {if (A == null || B == null) return false;return helper(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);}boolean helper(TreeNode A, TreeNode B) {if (B == null) return true;if (A == null || A.val != B.val) return false;return helper(A.left, B.left) && helper(A.right, B.right);}
}

解法 2:双重 BFS(很慢)

class Solution {public boolean isSubStructure(TreeNode A, TreeNode B) {if (A == null || B == null) return false;Queue<TreeNode> queue = new LinkedList<>() {{ offer(A); }};while (!queue.isEmpty()) {TreeNode node = queue.poll();if (node.val == B.val) if (helper(node, B))return true;if (node.left != null)queue.offer(node.left);if (node.right != null)queue.offer(node.right);}return false;}boolean helper(TreeNode A, TreeNode B) {Queue<TreeNode> queueA = new LinkedList<>() {{ offer(A); }};Queue<TreeNode> queueB = new LinkedList<>() {{ offer(B); }};while (!queueB.isEmpty()) {TreeNode nodeA = queueA.poll();TreeNode nodeB = queueB.poll();if (nodeA == null || nodeA.val != nodeB.val)return false;if (nodeB.left != null) {queueA.offer(nodeA.left);queueB.offer(nodeB.left);}if (nodeB.right != null) {queueA.offer(nodeA.right);queueB.offer(nodeB.right);}}return true;}
}

18.二叉树的镜像

题目:剑指 Offer 27. 二叉树的镜像

_解法 1:递归 DFS

这题很简单!很适合用来检验对递归的掌握程度。

// java
public TreeNode mirrorTree(TreeNode root) {if (root == null) return null;TreeNode leftNode = mirrorTree(root.left);TreeNode rightNode = mirrorTree(root.right);root.left = rightNode;root.right = leftNode;return root;
}

利用 Go 语言平行赋值的写法,可以省略暂存操作。

// go
func mirrorTree(root *TreeNode) *TreeNode {if root == nil {return nil}leftNode, rightNode := mirrorTree(root.Left), mirrorTree(root.Right)root.Left, root.Right = rightNode, leftNodereturn root
}

解法 2:辅助栈

题解:剑指 Offer 27. 二叉树的镜像(递归 / 辅助栈,清晰图解)

// java
public TreeNode mirrorTree1(TreeNode root) {if (root == null) return null;Stack<TreeNode> stack = new Stack<>() {{ add(root); }};while (!stack.empty()) {TreeNode node = stack.pop();if (node.left != null)stack.add(node.left);if (node.right != null)stack.add(node.right);TreeNode tmp = node.left;node.left = node.right;node.right = tmp;}return root;
}

19. 对称的二叉树

题目:剑指 Offer 28. 对称的二叉树

_解法 1:暴力

注意,这里写的获取二叉树的镜像涉及到一些 Java 值传递的概念,不可以在 root 上直接修改,需要创建一个新的对象,或者 root = new TreeNode(root.val) 也是可以的。

class Solution {public boolean isSymmetric(TreeNode root) {if (root == null) return true;TreeNode newTree = mirrorTree(root);return isSameTree(root, newTree);}/*** 判断两棵二叉树是否相同*/public boolean isSameTree(TreeNode p, TreeNode q) {if (p == null && q == null) return true;if ((p != null && q ==null) || (p == null && q != null))return false;if (p.val != q.val)return false;return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);}/*** 获得二叉树的镜像*/TreeNode mirrorTree(TreeNode root) {if (root == null)  return null;TreeNode leftNode = mirrorTree(root.left);TreeNode rightNode = mirrorTree(root.right);TreeNode node = new TreeNode(root.val);node.left = rightNode;node.right  = leftNode;return node;}
}

解法 2:递归

题解:面试题28. 对称的二叉树(递归,清晰图解)

1、递归的函数要干什么?

  • 函数的作用是判断传入的两个树是否镜像。
  • 输入:TreeNode left, TreeNode right
  • 输出:是:true,不是:false

2、递归停止的条件是什么

  • 左节点和右节点都为空 -> 倒底了都长得一样 ->true
  • 左节点为空的时候右节点不为空,或反之 -> 长得不一样-> false
  • 左右节点值不相等 -> 长得不一样 -> false

3、从某层到下一层的关系是什么

  • 要想两棵树镜像,那么一棵树左边的左边要和二棵树右边的右边镜像,一棵树左边的右边要和二棵树右边的左边镜像
  • 调用递归函数传入左左和右右
  • 调用递归函数传入左右和右左
  • 只有左左和右右镜像且左右和右左镜像的时候,我们才能说这两棵树是镜像的

4、调用递归函数

  • 我们想知道它的左右孩子是否镜像,传入的值是 root 的左孩子和右孩子。
  • 这之前记得判个 root==null。
class Solution {public boolean isSymmetric(TreeNode root) {if (root ==null) return true;return isMirror(root.left, root.right);}/*** 判断传入的两颗树是否镜像*/boolean isMirror(TreeNode left, TreeNode right) {if (left == null && right == null) return true;if (left == null || right == null) return false;if (left.val != right.val) return false;return isMirror(left.left, right.right) && isMirror(left.right, right.left);}
}

《剑指 Offer I》刷题笔记 11 ~ 19 题相关推荐

  1. 《剑指offer》刷题笔记(发散思维能力):求1+2+3+...+n

    <剑指offer>刷题笔记(发散思维能力):求1+2+3+-+n 转载请注明作者和出处:http://blog.csdn.net/u011475210 代码地址:https://githu ...

  2. 【LeetCode】《剑指Offer》第Ⅰ篇⊰⊰⊰ 3 - 11题

    [LeetCode]<剑指Offer>第Ⅰ篇⊰⊰⊰ 3 - 11题 文章目录 [LeetCode]<剑指Offer>第Ⅰ篇⊰⊰⊰ 3 - 11题 03. 数组中重复的数字(ea ...

  3. 《剑指offer》刷题——【链表】从尾到头打印链表

    <剑指offer>刷题--[链表]-<从尾到头打印链表> 问题分析: 递归实现: 1. 无返回值 2. 有返回值(ArrayList) 问题分析: 从头到尾打印链表比较简单,那 ...

  4. 《剑指Offer》刷题之最小的K个数

    <剑指Offer>刷题之最小的K个数 我不知道将去向何方,但我已在路上! 时光匆匆,虽未曾谋面,却相遇于斯,实在是莫大的缘分,感谢您的到访 ! 题目: 给定一个数组,找出其中最小的K个数. ...

  5. 《剑指offer》刷题总结

    从三月初开始刷剑指offer上面的题,到现在花了近二十天的时间终于刷完了.应该说,掌握上面的技巧应付一些公司面试题和小公司的笔试题是完全没有问题的.之前参加一个公司笔试,算法题就有一题是剑指offer ...

  6. 【剑指Offer】个人学习笔记_61_扑克牌中的顺子

    目录 题目: [剑指 Offer 61. 扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/) 题目分 ...

  7. 【剑指Offer】个人学习笔记_46_把数字翻译成字符串

    目录 题目: [剑指 Offer 46. 把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan- ...

  8. 【剑指Offer】个人学习笔记_38_字符串的排列

    目录 题目: [剑指 Offer 38. 字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/) 题目分析 初始解 ...

  9. 【剑指Offer】个人学习笔记_41_数据流中的中位数

    目录 题目: [剑指 Offer 41. 数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lc ...

最新文章

  1. Android学习笔记之ProgressDialog
  2. 对‘pthread_create’未定义的引用_【学习贴士】引用文献不积极,APA Guideline 帮助你...
  3. 洛谷 - P2045 - 方格取数加强版 - 费用流
  4. Deepin 20.2.3系统标题栏及其按钮美化
  5. 杭电oj 1001 c++版本
  6. mysql 8创建远程访问用户以及连接mysql速度慢的解决方法
  7. Jvm内存分析入门篇
  8. frameset框架如何使左边页面显示,隐藏?wj-wangjun
  9. 机器学习——下采样(under-sampling)
  10. overfeat 测试
  11. 计算机内存调用优化,Memory Cleaner——简单好用的Windows内存优化工具
  12. 认知LTE簇优化和全网优化
  13. c字打头的语言英语单词,C字开头的励志的英文单词要C字开头的~例如Champion,Confidence,...-c英语开头名词-英语-柯拿拷同学...
  14. c语言字符串输出大写字母个数,欧洲区预选赛视频直播 -官方网站
  15. mysql 性能优化方向
  16. 如何使用QQ群日历和群活动进行会议室安排
  17. 计算机硬盘格式化了如何恢复出厂设置,电脑恢复出厂设置和格式化有什么区别...
  18. ssm+redis整合(通过cache方式)
  19. 学生体育运动主题网页设计——兵乓球国乒网(纯HTML+CSS代码)
  20. ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程

热门文章

  1. 做自媒体花式撸收益?
  2. 如何让你的员工有闭环思维?
  3. 近些年CPU的性能是不是快到天花板了?
  4. i59400f能带动2070s吗?
  5. Failed building wheel for scandir 解决方案
  6. 【Java开发规范】禁止在 foreach 循环里进行元素的 remove/add 操作
  7. sql隐式转换_SQL Server中的隐式转换
  8. 德鲁伊 oltp oltp_内存中OLTP系列–简介
  9. 如何创建和自定义SQL Server模板
  10. power bi 地图_如何使用Power BI创建地理地图-填充地图和气泡地图