剑指offer_面试思路-画图-举例

前言:当前内容只用来回忆复习,java编写,代码尽可能带注释,部分题目附上解题思路。如有错误,请指出,谢谢。

1、二叉树的镜像

题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树

	   8
	   /  \
	  6   10
	 / \  / \
	5  7 9 11
	镜像二叉树
	    8
	   /  \
	  10   6
	 / \  / \
	11 9 7  5
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public void Mirror(TreeNode root) {
        // 排除空指针
        if(root == null)
            return;
        // 思路是递归,自底向上,左右子树互换
        // 将root的左子树变换为镜像
        Mirror(root.left);
        // 将root的右子树变换为镜像
        Mirror(root.right);
        // 当前节点的左右子树互换
        TreeNode p = root.left;
        root.left = root.right;
        root.right = p;
        return;
    }
}

2、顺时针打印矩阵,这题不是特难,但很难写好

题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

1  2  3  4 
5  6  7  8 
9  10 11 12 
13 14 15 16

2.1 这代码很烂很乱,但好歹是自己的哭了,主要思路,到达边界转换方向,控制不越界

反思:变量在变,然后又想用,利用很混乱。

public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> ret = new ArrayList<Integer>();
       // 排除空指针以及内容为空
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0)
            return ret;
        int width = matrix[0].length;
        int height = matrix.length;
        // 从左往右走到顶
        // 向下移动一位,从上往下走到顶
        // 向左移动一位,从右往左走到顶
        // 向上移动一位,从下往上走到顶-1
        for(int i = 0; i < height; i++){
            int j = 0 + i; // 改变顶的位置
            // 转换方向,判断是否越界
            if(j >= width-i)
                break;
            for(; j < width - i; j++){
                ret.add(matrix[i][j]);
                // 一旦达到边界就需跳出循环
                if(j == width - i - 1)
                    break;
            }
            int m = i + 1; // 向下移动一位
            // 每一次转换方向都要判断是否越界
            if(m >= height -i)
                break;
            // 向下走
            for(; m < height - i; m++){
                ret.add(matrix[m][j]);
                // 一旦达到边界就需跳出循环
                if(m == height - i - 1)
                    break;
            }
            int n = j - 1; // 向左移动一位
             // 每一次转换方向都要判断是否越界
            if(n < i)
                break;
            // 向左走
            for(; n >= 0 + i; n--){
                ret.add(matrix[m][n]);
                // 一旦达到边界就需跳出循环
                if(n == i)
                    break;
            }
            int k = m - 1;// 向上移动一位
            // 每一次转换方向都要判断是否越界
            if(k <= i)
                break;
            // 向上走
            for(; k > 0 + i; k--){
                ret.add(matrix[k][n]);
                if(k == i+1)
                    break;
            }
            // 当前已经转过一圈了,每转完一圈,顶的位置都会变
        }
        return ret;
    }
}

2.2 这个思路清晰,但感觉方法中有些累赘

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return list;
        }
        int up = 0;
        int down = matrix.length-1;
        int left = 0;
        int right = matrix[0].length-1;
        while(true){
            // 最上面一行
            for(int col=left;col<=right;col++){
                list.add(matrix[up][col]);
            }
            // 向下逼近
            up++;
            // 判断是否越界
            if(up > down){
                break;
            }
            // 最右边一行
            for(int row=up;row<=down;row++){
                list.add(matrix[row][right]);
            }
            // 向左逼近
            right--;
            // 判断是否越界
            if(left > right){
                break;
            }
            // 最下面一行
            for(int col=right;col>=left;col--){
                list.add(matrix[down][col]);
            }
            // 向上逼近
            down--;
            // 判断是否越界
            if(up > down){
                break;
            }
            // 最左边一行
            for(int row=down;row>=up;row--){
                list.add(matrix[row][left]);
            }
            // 向右逼近
            left++;
            // 判断是否越界
            if(left > right){
                break;
            }
        }
        return list;
    }
}

2.3 这个考虑了最后遍历的情况,只剩单行或单列,确保最后不重复遍历,压缩边界的思路更好

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {

        ArrayList<Integer> result = new ArrayList<>();
        if(matrix == null)return result;

        int low = 0;
        int high = matrix.length-1;
        int left = 0;
        int right = matrix[0].length-1;
        while(low <= high && left <= right){

            //向右
            for(int i=left; i <= right; i++)
                result.add(matrix[low][i]);

            //向下
             for(int i = low+1; i <= high; i++)
                 result.add(matrix[i][right]);

            //向左 有可能出现特殊的情况只有一行,为了避免重复访问
            if(low < high){
                for(int i= right-1; i >= left; i--)
                result.add(matrix[high][i]);
            }

            //向上 有可能出现特殊的情况只有一列,为了避免重复访问
            if(left < right){
                for(int i = high-1; i >= low+1; i--)
                result.add(matrix[i][left]);
            }

            low++;
            high--;
            left++;
            right--;
        }
        return result;
    }
}

2.4 这个思路特别新意,结合方向转换数组与访问确认数组

import java.util.ArrayList;
public class Solution {
    // 走的方向:向右、向下、向左、向上
    private final int[] dx = {0, 1, 0, -1};
    private final int[] dy = {1, 0, -1, 0};

    public ArrayList<Integer> printMatrix(int[][] matrix) {
        int n = matrix.length, m = matrix[0].length;
        boolean[][] vis = new boolean[n][m];
        ArrayList<Integer> list = new ArrayList<>();

        int x = 0, y = 0, dir = 0;
        while (x >= 0 && x < n && y >= 0 && y < m && !vis[x][y]) {
            list.add(matrix[x][y]);
            vis[x][y] = true;

            // 试着继续向dir的方向走
            while (x + dx[dir] >= 0 && x + dx[dir] < n && y + dy[dir] >= 0 && y + dy[dir] < m && !vis[x + dx[dir]][y + dy[dir]]) {
                x += dx[dir];
                y += dy[dir];
                list.add(matrix[x][y]);
                vis[x][y] = true;
            }
            // 走不动了换方向
            dir = (dir + 1) % 4;
            x += dx[dir];
            y += dy[dir];
        }
        return list;
    }
}

3、包含min函数的栈

题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
反复尝试就辅助栈思路最好,关键如何快速找出最小值

import java.util.Stack;
public class Solution {
    Stack<Integer> stackTotal = new Stack<Integer>(); // 原栈
    Stack<Integer> stackLittle = new Stack<Integer>(); // 辅助栈,存储原栈的最小值序列,重复值可能较多,元素个数与原栈相等
    
    public void push(int node) {
        stackTotal.push(node);
        if(stackLittle.isEmpty() ){
            // 如果辅助栈为空,直接入栈
            stackLittle.push(node);
        }
        else{
            int minim = stackLittle.peek();
            if(node <= minim){
                // 若待添加元素小于或等于辅助栈栈顶元素
                stackLittle.push(node);
            }
            else{
                // 若待添加元素大于辅助栈栈顶元素,辅助栈栈顶元素复制添加
                stackLittle.push(minim);
            }
        }
    }
    
    public void pop() {
        // 如果空栈,直接返回
        if(stackTotal.size() == 0)
            return;
        stackTotal.pop();
        stackLittle.pop();
    }
    
    public int top() {
        return stackTotal.peek();
    }
    
    public int min() {
        return stackLittle.peek();
    }
}

4、栈的压入、弹出序列

题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

import java.util.ArrayList;
import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
      // 排除空指针,以及序列长度不等情况
        if(pushA == null || popA == null || pushA.length != popA.length)
            return false;
        // 遍历两个序列
        int p1 = 0;// 压栈序列游标
        int p2 = 0; // 弹出序列游标
        Stack<Integer> stack = new Stack<>();
        while(p1 < pushA.length){
            int node = pushA[p1];
            if(node != popA[p2] ){
                // 不相等时,入栈且压栈游标加一
                stack.push(node);
                p1++;
            }
            else{
                // 相等时,先入栈后出栈,相当于压栈和出栈游标都加一
                p1++;
                p2++;
            }
        }
        // 压入顺序遍历完了,直接全部出栈
        while(!stack.isEmpty() ){
            if(stack.peek() == popA[p2]){
                // 尝试出栈过程中,出栈元素等于当前弹出序列元素,出栈并弹出序列游标加一
                stack.pop();
                p2++;
            } 
            else{
                // 尝试出栈过程中,任意出栈元素不等于当前弹出序列元素
                return false;
            }
        }
        // 最终弹出顺序满足入栈顺序,返回true
        return true;
    }
}

5、从上往下打印二叉树,需要好好复习deque的push和add区别

题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

import java.util.ArrayList;
import java.util.Deque;
import java.util.ArrayDeque;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        // 排除空指针异常
        if(root == null)
            return list;
        // 利用队列层层遍历
        Deque<TreeNode> dq = new ArrayDeque<>();
        dq.add(root); // 添加头结点
        TreeNode node = null; // 辅助结点
        while(!dq.isEmpty() ){
            node = dq.pollFirst();
            list.add(node.val); // 打印结点值
            if(node.left != null)
                dq.add(node.left);
            if(node.right != null)
                dq.add(node.right);
        }
        return list;
    }
}

6、二叉搜索树的后序遍历序列

题目描述
输入一个非空整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        // 排除null和空数组
        if(sequence == null || sequence.length == 0)
            return false;
        // 分析,二叉搜索树的后序遍历,是先遍历左子树,再右子树,后父结点。
        // 无论左右子树其中之一是否存在,肯定是有父结点的。左子树的所有值都小于父结点值,右子树的所有值都大于父结点值
        // 一个后序序列中可以确认的是,最右元素肯定是父结点,左边先是左子树结点,然后是右子树结点,
        // 那么,肯定存在一个分切点分开左右子树。
        // 找到分切点后,满足值大小关系就可以认为真,当然应该逐层判断
        return helpVerify(sequence, 0, sequence.length - 1);
    }
    public boolean helpVerify(int[] seq, int start, int end){
        // 这里必须start>end为true才行,因为序列[7 6]切出来左子树就是(seq, 0, -1)
        if(start >= end )
            return true;
        int parentVal = seq[end];
        int split = start;
        // 找到切分点
        while(seq[split] < parentVal && split < end){
            split++;
        }
        // 判断切分点右边是不是都大于父结点值
        for(int i = split; i < end; i++){
            if(seq[i] <= parentVal)
                return false;
        }
        // 继续递归判断下一层是不是满足后序遍历
        return helpVerify(seq, start, split-1) && helpVerify(seq, split, end - 1);
    }
}

7、二叉树中和为某一值的路径,血的教训,参数传值或传引用

题目描述
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。

7.1 个人理解,递归时传递参数最好保证传值,而不要传引用,(ArrayList) list.clone()克隆消除递归传参之间的依赖

特别注意
因为希望递归退出后,层之间不干扰,除非是需要传回处理结果而使用引用

当前先序遍历方法 preOrder(TreeNode root, int sum, int target, ArrayList<ArrayList > ret, ArrayList list)

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
        // 排除空指针
        if(root == null || target <= 0)
            return ret;
        // 采用先序遍历路径,若路径遍历到叶子结点,累加值仍小于目标值,退回到上一层
        // 若路径遍历到中途,累加值已经大于目标值,退出
        // 若路径遍历中,累加值已经等于目标值,保存,退回上一级
        int sum = 0; // 累加值
        ArrayList<Integer> list = new ArrayList<Integer>();
        preOrder(root, sum, target, ret, list);
        return ret;
    }
    public void preOrder(TreeNode root, int sum, int target, ArrayList<ArrayList<Integer> > ret, ArrayList<Integer> list){
        if(root == null)
            return;
        sum += root.val;
        list.add(root.val);
        // 若路径遍历到叶子结点,累加值仍小于目标值
        // 或当前路径累加值已经超过了目标值,退出
        if( sum > target  ){
            return;
        }
        // 找到了符合条件路径,添加后,退出
        if(root.left == null && root.right == null && sum == target ){
            ret.add(list); // 保存
            return;
        }
        preOrder(root.left, sum, target, ret, (ArrayList<Integer>) list.clone());
        preOrder(root.right, sum, target, ret, (ArrayList<Integer>) list.clone());
        return;
    }
}

7.2 递归解法,采用减法较加法少传一个参数

当前先序遍历方法 preOrder(TreeNode root, int target, ArrayList<ArrayList > ret, ArrayList list)

import java.util.ArrayList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
        // 排除空指针
        if(root == null || target <= 0)
            return ret;
        // 采用先序遍历路径,若路径遍历到叶子结点,累加值仍小于目标值,退回到上一层
        // 若路径遍历到中途,累加值已经大于目标值,退出
        // 若路径遍历中,累加值已经等于目标值,保存,退回上一级
        ArrayList<Integer> list = new ArrayList<Integer>();
        preOrder(root, target, ret, list);
        return ret;
    }
    public void preOrder(TreeNode root, int target, ArrayList<ArrayList<Integer> > ret, ArrayList<Integer> list){
        if(root == null)
            return;
        // 若路径遍历到叶子结点,累加值仍小于目标值
        // 或当前路径累加值已经超过了目标值,退回上一层
        // 找到了符合条件路径,添加后,退回上一层
        list.add(root.val);
        target -= root.val;
        // 当前路径累加值已经超过了目标值
        if(target < 0){
            return;
        }
        if(target == 0 && root.left == null && root.right == null ){
            ret.add(list); // 保存
            return;
        }
        preOrder(root.left, target, ret, (ArrayList<Integer>)list.clone() );
        preOrder(root.right, target, ret, (ArrayList<Integer>)list.clone() );
        return;
    }
}

7.3 非递归解法,以后有时间再补充

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值