面试题3:数组中重复的数字

题目一:长度为n的数组里的数字在0到n-1的范围

题目一:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2, 3, 1, 0, 2, 5, 3},那么对应的输出是重复的数字2或者3。

  • 算法
    • 1.遍历数组,把所有数字归到原位,即使得nums[i] = i
    • 2.当遇到nums[i] != i的数字时,判断nums[i]和nums[nums[i]]是否相等
      • 2.1 如果相等,那它就是重复的数字
      • 2.2 如果不相等,就把当前不应该在这个位置的数字归位,然后循环直到把应该在这个位置的数字归位
public int findRepeatNumber(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        while (nums[i] != i) {
            if (nums[i] == nums[nums[i]]) {
                return nums[i];
            }
            int temp = nums[i];
            nums[i] = nums[temp];
            nums[temp] = temp;
        }
    }
    return -1;
}

题目二:长度为n的数组里的数字在1到n-1的范围

题目二:在一个长度为n+1的数组里的所有数字都在1到n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但不能修改输入的数组。例如,如果输入长度为8的数组{2, 3, 5, 4, 3, 2, 6, 7},那么对应的输出是重复的数字2或者3。

  • 算法
    • 二分查找法
    • 1.每次循环统计数组中值在[start middle]范围的元素的个数
    • 2.如果范围大于 middle - start + 1,说明重复的数在左半部分,否则在右半部分;重复查找
public int findRepeatNumber(int[] nums) {
    int start = 1;
    int end = nums.length - 1;
    while (start <= end) {
        int middle = (start + end) >> 1;
        int count = countRange(nums, start, middle);

        if (start == end) {
            if (count > 1) {
                return start;
            } else {
                break;
            }
        }

        if (count > (middle - start + 1)) {
            end = middle;
        } else {
            start = middle + 1;
        }
    }
    return -1;
}

private int countRange(int[] nums, int start, int end) {
    int count = 0;
    for(int i = 0; i < nums.length; i++) {
        if (nums[i] >= start && nums[i] <= end) {
            count++;
        }
    }
    return count;
}

面试题4:二维数组中的查找

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

    1  2  3  4  5
    6  7  8  9  10
    11 12 13 14 15
    16 17 18 19 20
  • 算法
    • 1.从矩阵的右上角开始寻找而不是从矩阵的左上角开始寻找,便于判断下一步走向
    • 2.大于target的话,列数减一
    • 3.小于target的话,行数加一
public boolean findNumberIn2DArray(int[][] matrix, int target) {
    int rows = matrix.length, cols = matrix[0].length;
    int row = 0, col = cols - 1;
    while (row < rows && col >= 0) {
        if (matrix[row][col] > target) {
            col--;
        } else if (matrix[row][col] < target) {
            row++;
        } else {
            return true;
        }
    }
    return false;
}

面试题5:替换空格

题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.”。

  • 算法
    • 先遍历一遍统计空格个数,分配替换空格后新的char数组
public String replaceSpace(String s) {
    if (s == null) {
        return s;
    }

    int oldLength = s.length();
    int numberOfBlank = 0;
    for (char c : s.toCharArray()) {
        numberOfBlank += (c == ' ' ? 1 : 0);
    }

    if(numberOfBlank == 0) {
        return s;
    }

    int newLength = oldLength + (numberOfBlank << 1);
    int indexOfOld = oldLength-1;
    int indexOfNew = newLength-1;
    StringBuilder sb = new StringBuilder(newLength);
    while (indexOfOld >= 0) {
        if (s.charAt(indexOfOld) == ' ') {
            sb.insert(0, '0');
            sb.insert(0, '2');
            sb.insert(0, '%');
            indexOfNew -= 3;
        } else {
            sb.insert(0, s.charAt(indexOfOld));
        }
        indexOfOld--;

        if (indexOfNew == indexOfOld) {
            sb.insert(0, s.substring(0, indexOfNew+1));
            break;
        }
    }
    return sb.toString();
}

面试题6:从尾到头打印链表

题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。

  • 算法
    • 算法1.第一次遍历统计元素,第二次遍历保存到数组
    • 算法2.使用栈保存元素,再逐个弹出
// 不使用栈
public int[] reversePrint(ListNode head) {
    ListNode node = head;
    int count = 0;
    while (node != null) {
        count++;
        node = node.next;
    }

    int[] res = new int[count];
    node = head;
    for (int i = count - 1; i >= 0; --i) {
        res[i] = node.val;
        node = node.next;
    }
    return res;
}

// 使用栈
public int[] reversePrint(ListNode head) {
    if (head == null) {
        return new int[0];
    }

    Stack<Integer> stack = new Stack<Integer>();
    while (head != null) {
        stack.push(head.val);
        head = head.next;
    }
    int[] res = new int[stack.size()];
    int i = 0;
    while (!stack.empty()) {
        res[i++] = stack.pop();
    }

    return res;
}

// 使用栈或递归打印结果
public void reverseTraverseListNode(ListNode head) {
        if (head == null) {
            return;
        }

        Stack<Integer> stack = new Stack<>();
        while (head != null) {
            stack.push(head.val);
            head = head.next;
        }
        while (!stack.empty()) {
            System.out.print(stack.pop()+" ");
        }
}
public void reverseTraverseListNode(ListNode head) {
    if (head != null) {
        reverseTraverseListNode(head.next);
        System.out.print(head.val+" ");
    }
}

面试题7:重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列{4, 7, 2, 1, 5, 3, 8, 6},则重建出图2.6所示的二叉树并输出它的头结点。

              1
           /     \
          2       3  
         /       / \
        4       5   6
         \         /
          7       8
            图 2.6
  • 算法
    • 类似重建二叉树的统一算法
    • 1.找出根节点
    • 2.利用根节点计算左子树和右子树中的节点个数
    • 3.利用节点个数递归构建左子树和右子树
public TreeNode buildTreeByPreorderAndInorder(int[] preorder, int[] inorder) {
    if (preorder == null || inorder == null || preorder.length == 0 || inorder.length == 0) {
        return null;
    }
    if (preorder.length != inorder.length) {
        throw new RuntimeException("Invalid input");
    }

    return buildTreeByPreorderAndInorder(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}

private TreeNode buildTreeByPreorderAndInorder(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
    TreeNode root = new TreeNode(preorder[preStart]);
    if (preStart == preEnd) {
        if (preorder[preStart] == inorder[inStart]) {
            return root;
        } else {
            throw new RuntimeException("Invalid input");
        }
    }

    int rootIndex = inStart;
    while (rootIndex < inEnd && inorder[rootIndex] != preorder[preStart]) {
        rootIndex++;
    }
    if (rootIndex == inEnd && inorder[rootIndex] != preorder[preStart]) {
        throw new RuntimeException("Invalid input");
    }

    int leftLength = rootIndex - inStart;
    int rightLength = inEnd - rootIndex;
    if (leftLength > 0) {
        root.left = buildTreeByPreorderAndInorder(preorder, preStart+1, preStart+leftLength, inorder, inStart, rootIndex-1);
    }
    if (rightLength > 0) {
        root.right = buildTreeByPreorderAndInorder(preorder, preStart+leftLength+1, preEnd, inorder, rootIndex+1, inEnd);
    }
    return root;
}

面试题8:二叉树的下一个结点

题目:给定一棵二叉树和其中的一个结点,如何找出中序遍历顺序的下一个结点?树中的结点除了有两个分别指向左右子结点的指针以外,还有一个指向父结点的指针。

  • 算法:结点有指向父结点的引用
    • 1.如果右子树不为空,下一个结点就是右子树中最左下方的结点
    • 2.如果右子树为空
      • 2.1如果当前结点没有父结点,它就是根节点,下一个结点就是null
      • 2.2如果当前结点是父结点的左子结点,加上本身右子树为空,下一个结点就是父结点
      • 2.3如果当前结点是父结点的右子结点,需要向上追溯直到当前结点是父结点的左子结点
public class TreeNode2 {
    public int val;
    public TreeNode2 left;
    public TreeNode2 right;
    public TreeNode2 parent;
    public TreeNode2(int x) { val = x; }
}

public TreeNode2 nextNodeOfInorder(TreeNode2 node) {
    if (node == null) {
        return null;
    }

    if (node.right != null) {
        TreeNode2 rightTree = node.right;
        while (rightTree.left != null) {
            rightTree = rightTree.left;
        }
        return rightTree;
    } else {
        TreeNode2 cur = node;
        TreeNode2 parent = node.parent;
        while (parent != null && cur == parent.right) {
            cur = parent;
            parent = parent.parent;
        }
        return parent;
    }
}
  • 算法:标准二叉树
    • 1.如果右子树不为空,下一个结点就是右子树中最左下方的结点
    • 2.如果右子树为空
      • 2.1如果当前结点没有父结点,它就是根节点,下一个结点就是null
      • 2.2如果当前结点是父结点的左子结点,加上本身右子树为空,下一个结点就是父结点
      • 2.3如果当前结点是父结点的右子结点,需要向上追溯直到当前结点是父结点的左子结点
    • ps:寻找父节点的算法
      • 递归算法
      • 1.当root的左子树和右子树都不是node时,递归到左右子树寻找,然后判断寻找到与否
      • 2.当root的左子树和右子树有一个是node时,父节点就是root
public TreeNode nextNodeOfInorder(TreeNode root, TreeNode node) {
    if (root == null || node == null) {
        return null;
    }

    if (node.right != null) {
        TreeNode rightTree = node.right;
        while (rightTree.left != null) {
            rightTree = rightTree.left;
        }
        return rightTree;
    } else {
        TreeNode cur = node;
        TreeNode parent = parentNode(root, node);
        while (parent != null && cur == parent.right) {
            cur = parent;
            parent = parentNode(root, parent);
        }
        return parent;
    }
}

private TreeNode parentNode(TreeNode root, TreeNode node) {
    if (root == null || root.left == null && root.right == null) {
        return null;
    }

    TreeNode parent = root;
    if (root.left != node && root.right != node) {
        TreeNode leftTree = parentNode(root.left, node);
        TreeNode rightTree = parentNode(root.right, node);
        if (leftTree != null) {
            parent = leftTree;
        } else if (rightTree != null) {
            parent = rightTree;
        } else {
            parent = null;
        }
    }
    return parent;
}

面试题9:栈和队列相互实现

题目一:用两个栈实现一个队列

题目一:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。

  • 算法
    • 1.添加时向第一个栈添加
    • 2.删除时从第二个栈删除,如果是空,全部元素换栈删除栈顶元素
class CQueue {
    public CQueue() {
    }

    public void appendTail(int value) {
    }

    public int deleteHead() {
    }
}
class CQueue <T> {
    Stack<T> stack1 = new Stack<>();
    Stack<T> stack2 = new Stack<>();
    public CQueue() {

    }

    public void appendTail(T value) {
        stack1.push(value);
    }

    public T deleteHead() {
        if (stack2.empty()) {
            while (!stack1.empty()) {
                stack2.push(stack1.pop());
            }
        }
        if (stack2.empty()) {
            throw new RuntimeException("Empty Queue");
        }
        return stack2.pop();
    }
}

题目二:用两个队列实现一个栈

  • 算法
    • 1.添加时向非空的那个队列添加
    • 2.删除时全部元素改变队列,最后一个元素删除;所以任何时刻一定有一个队列为空
class CStack <T> {
    Queue<T> queue1 = new LinkedList<>();
    Queue<T> queue2 = new LinkedList<>();
    public CStack() {

    }

    public void appendTail(T value) {
        if (queue2.isEmpty()) {
            queue1.add(value);
        } else {
            queue2.add(value);
        }
    }

    public T deleteTop() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            throw new RuntimeException("Empty Stack");
        } else if (queue1.isEmpty()) {
            while (queue2.size() > 1) {
                queue1.add(queue2.poll());
            }
            return queue2.poll();
        } else {
            while (queue1.size() > 1) {
                queue2.add(queue1.poll());
            }
            return queue1.poll();
        }
    }
}