【剑指OFFER】——Java实现(面试题27-38)

面试题27:二叉树的镜像
解题思路:遍历树,先交换根点的左右子节点的位置,继续遍历左右子节点,交换子节点的左右子节点位置,依次遍历完成。

面试题28:对称的二叉树
解题思路:利用树的遍历,前序遍历的特点是先遍历左子节点再遍历右子节点,重新设计一个对称遍历(先遍历右子节点再遍历左子节点),比较两个遍历过程中的值。

面试题29:顺时针打印矩阵

    public void twentyNinth() {
        int[][] matrix = {
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 16},
                {17, 18, 19, 20},
                {21, 22, 23, 24}
        };
        printMatrixClockwisely(matrix);
    }

    private void printMatrixClockwisely(int[][] matrix) {
        int rowMin = 0,
                colMin = 0,
                rowMax = matrix.length - 1,
                colMax = matrix[0].length - 1;
        int rowNum = 0;
        int colNum = 0;
        while (rowMin <= rowMax && colMin <= colMax) {
            if (colMin <= colMax) {
                for (int i = colNum; i <= colMax; i++) {
                    System.out.println(matrix[rowNum][i]);
                }
                colNum = colMax;
                rowMin++;
            }
            if (rowMin <= rowMax) {
                for (int i = rowNum + 1; i <= rowMax; i++) {
                    System.out.println(matrix[i][colNum]);
                }
                rowNum = rowMax;
                colMax--;
            }
            if (colMin <= colMax) {
                for (int i = colNum - 1; i >= colMin; i--) {
                    System.out.println(matrix[rowNum][i]);
                }
                colNum = colMin;
                rowMax--;
            }
            if (rowMin <= rowMax) {
                for (int i = rowNum - 1; i >= rowMin; i--) {
                    System.out.println(matrix[i][colNum]);
                }
                rowNum = rowMin;
                colMin++;
                colNum++;
            }
        }
    }

面试题30:包含 min 函数的栈
题目:定义一个栈,调用 min、push、pop 的时间复杂度都是 O(1)。
解题思路:另外维护一个辅助栈用于存储最小值,push一个值时比较辅助栈的栈顶元素,若等于栈顶,插入栈顶元素到辅助栈,若小于,插入push值到辅助栈,若大于不操作。在pop的时候,比较pop出来的值和辅助栈顶元素,若大于不操作,等于就移除栈顶元素。min,直接返回辅助栈顶的元素。

面试题31:栈的压入、弹出序列
题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否是该栈的弹出顺序。
解题思路:利用一个辅助栈,按照压入顺序入辅助栈,按照弹出顺序出栈,若辅助栈弹出的元素与弹出顺序不一致即不满足,若结束后辅助栈仍然不为空也表示不满足。

    public void thirtyFirst() {
        int[] push = {1, 2, 3, 4, 5, 6};
        int[] pop = {4, 5, 6, 3, 2, 1};
        boolean flag = isPopOrder(push, pop, push.length);
        System.out.println(flag);
        int[] pop1 = {1, 4, 6, 5, 3, 2};
        flag = isPopOrder(push, pop1, push.length);
        System.out.println(flag);
        int[] pop2 = {1, 4, 6, 5, 3, 1};
        flag = isPopOrder(push, pop2, push.length);
        System.out.println(flag);
    }

    private boolean isPopOrder(int[] push, int[] pop, int length) {
        if (push == null || pop == null || push.length != pop.length) {
            return false;
        }
        int i = 0, j = 0;
        Stack<Integer> dataStack = new Stack<>();
        for (i = 0; i < length; i++) {
            dataStack.push(push[i]);
            while (j < length && !dataStack.isEmpty()) {
                Integer peek = dataStack.peek();
                if (peek == pop[j]) {
                    dataStack.pop();
                    j++;
                } else {
                    break;
                }
            }
        }
        return dataStack.isEmpty();
    }

面试题32:从上到下打印二叉树
题目一:不分行从上到下打印二叉树。
题目二:分行从上到下打印二叉树。
题目三:之字形打印二叉树。
解题思路:利用辅助的队列,每层的所有元素入队列,记录层内的元素个数,元素出队列并打印,然后将左右子节点入队列,后面的逻辑类似。

    public void thirtySecond3() {
        TreeNode root = createNode(0, 7, 0, 7);
        printTreeZigzag(root);
    }

    private void printTreeZigzag(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> treeNodes1 = new Stack<>();
        Stack<TreeNode> treeNodes2 = new Stack<>();
        treeNodes1.push(root);
        int current = 0;
        TreeNode pop;
        while (!treeNodes1.isEmpty() || !treeNodes2.isEmpty()) {
            if (current == 1) {
                while (!treeNodes1.isEmpty()) {
                    pop = treeNodes1.pop();
                    System.out.print(pop.getValue() + " ");
                    if (pop.getLeft() != null) {
                        treeNodes2.push(pop.getLeft());
                    }
                    if (pop.getRight() != null) {
                        treeNodes2.push(pop.getRight());
                    }
                }
                System.out.println();
            }
            if (current == 0) {
                while (!treeNodes2.isEmpty()) {
                    pop = treeNodes2.pop();
                    System.out.print(pop.getValue() + " ");
                    if (pop.getRight() != null) {
                        treeNodes1.push(pop.getRight());
                    }
                    if (pop.getLeft() != null) {
                        treeNodes1.push(pop.getLeft());
                    }
                }
                System.out.println();
            }
            current = 1 - current;
        }
    }

面试题33:二叉搜索树的后续遍历序列
题目:输入一个整数数组,判断这个数组是否是二叉搜索树的后序遍历。
解题思路:后序遍历的特点是先遍历左右子节点再遍历分支节点,数组的最后一个值就是根节点,从数组的下标0开始所有小于根节点的元素都是根节点的左子树的节点,大于的都是根节点的右子树中的节点,若右子树中存在比根节点小的就代表不满足,然后递归检查左右子树。

    public void thirtyThird() {
        int[] arrays = {5, 7, 6, 9, 11, 10, 8};
        boolean b = verifyTreeBST(arrays, 0, arrays.length - 1);
        System.out.println(b);
    }

    private boolean verifyTreeBST(int[] arrays, int begin, int end) {
        if (arrays == null || begin > end) {
            return false;
        }
        if (begin == end) {
            return true;
        }
        int i = begin;
        for (; i < end; i++) {
            if (arrays[i] > arrays[end]) {
                break;
            }
        }
        int j = i;
        for (; j < end; j++) {
            if (arrays[j] < arrays[end]) {
                return false;
            }
        }
        boolean left = true;
        if (i - begin > 1) {
            left = verifyTreeBST(arrays, begin, i-1);
        }
        boolean right = true;
        if (end - i > 1) {
            right = verifyTreeBST(arrays, i, end - 1);
        }
        return left && right;
    }

面试题34:二叉树中和为某一值的路径
题目:输入一棵二叉树和一个整数,输出节点值和为输入值的所有路径。从根节点开始一直往下到叶子结点所进过的节点形成一条路径。
解题思路:利用一个辅助栈存储前序遍历经过的节点,若到达叶子结点就判断路径和是否满足条件,不满足就弹出此叶子节点返回上一级。

    public void thirtyForth() {
        TreeNode root = createNode(0, 7, 0, 7);
        Stack<Integer> dataStack = new Stack<>();
        findPath(root, dataStack, 0, 9);
    }

    private void findPath(TreeNode node, Stack<Integer> dataStack, int currentNum, int exceptedNum) {
        if (node == null) {
            return;
        }
        currentNum += node.getValue();
        //中序遍历
        dataStack.push(node.getValue());
        boolean isLeaf = node.getLeft() == null && node.getRight() == null;
        if (isLeaf && currentNum == exceptedNum) {
            dataStack.forEach(System.out::print);
        }
        if (node.getLeft() != null) {
            findPath(node.getLeft(), dataStack, currentNum, exceptedNum);
        }
        if (node.getRight() != null) {
            findPath(node.getRight(), dataStack, currentNum, exceptedNum);
        }
        dataStack.pop();
    }

面试题35:复杂链表的复制
题目:复杂链表中除了有一个 next 指针,还有一个 m_pSibling 指向链表中任意一个节点。
解题方案1:先复制整个 next 链表,再沿着 next 指针设置每个节点的 m_pSibling,时间复杂度O(n2);
解题方案2:不沿着 next 指针找 m_pSibling,使哈希表存储,空间复杂度O(n);
解题方案3:在原链表的基础上,在每个节点后下个节点前插入一个新节点,维护一个长的链表,再设置 m_pSibling ,最后断开链表。

    @Data
    @Builder
    private static class ComplexNode {
        private Integer value;
        private ComplexNode next;
        private ComplexNode sibling;

        @Override
        public String toString() {
            return "ComplexNode{" +
                    "value=" + value +
                    ", next=" + next +
                    ", sibling=" + (sibling == null ? null : sibling.getValue().toString()) +
                    '}';
        }
    }
    
    public void thirtyFifth() {
        ComplexNode headNode = createComplexNode();
        //复制链表中的每一个节点
        CloneNodes(headNode);
        //设置复制的节点的 sibling 指针
        ConnectSibling(headNode);
        //重新连接,拆分为两个链表
        ComplexNode cloneHeadNode = ReconnectNodes(headNode);
        System.out.println(cloneHeadNode);
    }

    private ComplexNode createComplexNode() {
        //next 1->2->3->5
        //sibling 2->5
        //sibling 3->1
        ComplexNode node1 = ComplexNode.builder().value(1).build();
        ComplexNode node2 = ComplexNode.builder().value(2).build();
        ComplexNode node3 = ComplexNode.builder().value(3).build();
        ComplexNode node5 = ComplexNode.builder().value(5).build();
        node1.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node5);
        node2.setSibling(node5);
        node3.setSibling(node1);
        return node1;
    }

    private void CloneNodes(ComplexNode headNode) {
        while (headNode != null) {
            ComplexNode cloneNode = ComplexNode.builder()
                    .value(headNode.getValue())
                    .next(headNode.getNext())
                    .sibling(headNode.getSibling())
                    .build();
            headNode.setNext(cloneNode);
            headNode = cloneNode.getNext();
        }
    }

    private void ConnectSibling(ComplexNode headNode) {
        while (headNode != null) {
            ComplexNode cloneNode = headNode.getNext();
            if (headNode.getSibling() != null) {
                cloneNode.setSibling(headNode.getSibling().getNext());
            }
            headNode = cloneNode.getNext();
        }
    }

    private ComplexNode ReconnectNodes(ComplexNode headNode) {
        ComplexNode cloneHeadNode = null;
        ComplexNode node = null;
        if (headNode != null) {
            cloneHeadNode = headNode.getNext();
            node = cloneHeadNode;
        }
        while (headNode != null) {
            headNode.setNext(node.getNext());
            headNode = node.getNext();
            if (headNode != null) {
                node.setNext(headNode.getNext());
                node = node.getNext();
            } else {
                node.setNext(null);
            }
        }
        return cloneHeadNode;
    }

面试题36:二叉搜索树与双向链表
题目:将一棵二叉搜索树转换成一个排序的双向链表。

    public void thirtySixth() {
        TreeNode selectTree = createSelectTree();
        System.out.println(selectTree);
        TreeNode lastNodeInList = convertNode(selectTree, null);
        while (lastNodeInList != null && lastNodeInList.getLeft() != null) {
            lastNodeInList = lastNodeInList.getLeft();
        }
        System.out.println(lastNodeInList);
    }
    
    /**
     * 		10
     * 	 6      14
     * 4   8  12  16
     *
     * @return 二叉查找树
     */
    private static TreeNode createSelectTree() {
        TreeNode node4 = TreeNode.builder().value(4).build();
        TreeNode node8 = TreeNode.builder().value(8).build();
        TreeNode node6 = TreeNode.builder().value(6).left(node4).right(node8).build();
        TreeNode node12 = TreeNode.builder().value(12).build();
        TreeNode node16 = TreeNode.builder().value(16).build();
        TreeNode node14 = TreeNode.builder().value(14).left(node12).right(node16).build();
        TreeNode rootNode = TreeNode.builder().value(10).left(node6).right(node14).build();
        return rootNode;
    }

    private TreeNode convertNode(TreeNode treeNode, TreeNode lastOfList) {
        if (treeNode == null) {
            return null;
        }
        if (treeNode.getLeft() != null) {
            lastOfList = convertNode(treeNode.getLeft(), lastOfList);
        }
        if (lastOfList != null) {
            lastOfList.setRight(treeNode);
        }
        treeNode.setLeft(lastOfList);
        lastOfList = treeNode;
        if (treeNode.getRight() != null) {
            lastOfList = convertNode(treeNode.getRight(), lastOfList);
        }
        return lastOfList;
    }

面试题38:字符串的排列
题目1:打印字符串中所有字符的排列。

    public void thirtyEighth1() {
        //全排列就是从第一个数字起每个数分别与它后面的数字交换
        permutationCombination("abA".toCharArray(), 0);
    }

    private static void permutationCombination(char[] chars, int beginIndex) {
        if (chars == null) {
            return;
        }
        if (beginIndex == chars.length) {
            for (int i = 0; i < chars.length; i++) {
                System.out.print(chars[i]);
            }
            System.out.println();
        } else {
            for (int i = beginIndex; i < chars.length; i++) {
                char temp = chars[i];
                chars[i] = chars[beginIndex];
                chars[beginIndex] = temp;
                permutationCombination(chars, beginIndex + 1);
                // 下面的代码作用是换回来,此步骤必须
                temp = chars[i];
                chars[i] = chars[beginIndex];
                chars[beginIndex] = temp;
            }
        }
    }

题目2:打印字符串中所有字符的组合。

    public void thirtyEighth2() {
        char[] chars = "abcd".toCharArray();
        Stack<Character> stack = new Stack<>();
        for (int i = 1; i <= chars.length; i++) {
            permutation(chars, 0, i, stack);
        }
    }

    private static void permutation(char[] chars, int beginIndex, int m, Stack<Character> stack) {
        // 分别求n-1个字符串中长度为m-1的组合,以及n-1个字符串中长度为m的组合
        // 若组合中包含第一个字符,则在剩下的字符中选取m-1个字符;若不包含,则在剩下的n-1个字符中选取m个字符。
        if (m > 0) {
            if (beginIndex == chars.length) {
                return;
            }
            stack.push(chars[beginIndex]);
            permutation(chars, beginIndex + 1, m - 1, stack);
            stack.pop();
            permutation(chars, beginIndex + 1, m, stack);
        } else {
            System.out.println(stack.toString());
        }
    }

题目2:另类求解

    /**
     * 字符串长度为m的所有组合(位运算)
     */
    private static void permutation1(char[] chars) {
        if (chars == null) {
            return;
        }
        int length = chars.length;
        StringBuilder stringBuilder = new StringBuilder(length);
        // n位可以表示2^n个数,需要排除0 ,可能的组合结果有 (2^n)-1 种
        for (int i = 1; i < (1 << length); i++) {
            for (int j = 0; j < length; j++) {
                if ((i & (1 << j)) > 0) {
                    stringBuilder.append(chars[j]);
                }
            }
            System.out.println(stringBuilder.toString());
            stringBuilder.delete(0, length);
            // 需要确定那些位为1
        }
    }

题目3:八皇后问题

    public void thirtyEighth4() {
        permutationCombination("12345678".toCharArray(), 0);
    }

    private static void permutationCombination(char[] chars, int beginIndex) {
        if (chars == null) {
            return;
        }
        if (beginIndex == chars.length) {
            if (check(chars)) {
                for (int i = 0; i < chars.length; i++) {
                    System.out.print(chars[i]);
                }
                System.out.println();
            }
        } else {
            for (int i = beginIndex; i < chars.length; i++) {
                char temp = chars[i];
                chars[i] = chars[beginIndex];
                chars[beginIndex] = temp;
                permutationCombination(chars, beginIndex + 1);
                // 下面的代码作用是换回来,此步骤必须
                temp = chars[i];
                chars[i] = chars[beginIndex];
                chars[beginIndex] = temp;
            }
        }
    }

    private static boolean check(char[] chars) {
        for (int i = 0; i < chars.length; i++) {
            for (int j = i + 1; j < chars.length; j++) {
                if ((i - j == chars[i] - chars[j]) || (j - i == chars[i] - chars[j])) {
                    return false;
                }
            }
        }
        return true;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值