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

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

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)

扫描二维码关注公众号,回复: 13776463 查看本文章
// 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)-1
	for left < right {
    
    
		mid := left + (right-left)>>1
		if 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)-1
	for left < right {
    
    
		mid := left + (right-left)>>1
		if 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, leftNode
	return 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);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/123261610
今日推荐