剑指offer题解(持续更新中)

3-1 数组中重复的数字

1571133447774

每遍历数组中的一个数字,就让其归位(放置在正确的数组下标)。当在归位的过程中,发现该数组下标所存放的数字和当前要归位的数字相同时,则发生了重复,返回该数字。

空间复杂度O(1),时间复杂度O(n)。

public class FindDuplicateNum_3 {
    public static boolean findDuplicateNum(int[] arr, int length, int[] dup) {
        if (arr == null || length <= 0) {
            return false;
        }
        //时间复杂度O(n)
        for (int i = 0; i < length; i++) {
            //每个数字最多交换2次
            while (arr[i] != i) {
                if (arr[i] == arr[arr[i]]) {
                    dup[0] = arr[i];
                    return true;
                }
                swap(arr, i, arr[i]);
            }
        }
        return false;

    }

    private static void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

}

3-2 不修改数组找出重复数字

1570713605981

空间复杂度O(1),采用类似二分查找的算法,时间复杂度O(nlogn)。

思路:将1~ n上的数字划分成两块:1~ m和m+1~ n,然后统计数组中该区间上的数字个数,如果数字个数大于区间长度,则发生了重复,然后在该区间上继续二分,直至区间长度等于1。

//不修改数组找出重复数字
public static int findDuplicateNumNoEdit(int[] arr, int length) {
    if (arr == null || length <= 0) {
        return -1;
    }
    int start = 1;
    int end = length - 1;

    while (start <= end) {

        int mid = start + ((end - start) >> 1);
        int count = getCount(arr, length, start, mid);
        //System.out.println(mid+" "+count);
        if (start == end) {
            if (count > 1) {
                return start;
            } else {
                return -1;
            }
        }
        if (count > (mid - start + 1)) {
            end = mid;
        } else {
            start = mid + 1;
        }

    }
    return -1;
}

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

4 二维数组查找

1570761437213

从左下或者右上角开始查找,每次判断可以剔除一行或者是一列,时间复杂度O(n+m)

public static boolean Find(int target, int[][] array) {
    /*左下查找*/
    int rows = array.length;
    if (rows == 0) {
        return false;
    }
    int columns = array[0].length;
    if (columns == 0) {
        return false;
    }
    int column = 0;
    int row = rows - 1;
    //注意数组边界
    while (row >= 0 && column < columns) {
        if (target == array[row][column]) {
            return true;
        } else if (target < array[row][column]) {
            row--;
        } else {
            column++;
        }

    }
    return false;
}

5 替换空格

1570760015108

先统计出字符串中的空格数量,然后计算出替换后的字符串长度,从后往前遍历字符串,依次填充。

public static String replaceBlankSpace_2(String str) {
    if (str == null) {
        return null;
    }
    char[] chars = str.toCharArray();
    int count = 0;

    for (int i = 0; i < chars.length; i++) {
        if (chars[i] == ' ') {
            count++;
        }
    }
    int newLength = chars.length + (count << 1);
    int p1 = chars.length - 1;
    int p2 = newLength - 1;
    char[] newChars = new char[newLength];
    while (p1 >= 0) {
        if (chars[p1] == ' ') {
            newChars[p2--] = '0';
            newChars[p2--] = '2';
            newChars[p2--] = '%';
            p1--;
        } else {
            newChars[p2--] = chars[p1--];
        }
    }

    return String.valueOf(newChars);

}

6 从尾到头打印链表

1570762089940

使用栈,遍历一遍链表,将链表中的节点压栈,然后输出栈中的节点

import java.util.Stack;

public class FromHeadtoTailPrintLinkedList_6 {
    static class ListNode {
        int key;
        ListNode next;

        public ListNode(int key) {
            this.key = key;
        }
    }

    public static void fromHeadtoTailPrintLinkedListByStack(ListNode head) {
        if (head == null) {
            return;
        }
        Stack<ListNode> stack = new Stack();
        while (head != null) {
            stack.push(head);
            head = head.next;
        }

        while (!stack.isEmpty()) {
            System.out.print(stack.pop().key + " ");
        }

    }

    public static void fromHeadtoTailPrintLinkedListByRecursion(ListNode head) {
        if (head == null) {
            return;
        }

        fromHeadtoTailPrintLinkedListByStack(head.next);
        System.out.print(head.key + " ");

    }

    public static void main(String[] args) {
        ListNode head = new ListNode(0);
        head.next = new ListNode(1);
        head.next.next = new ListNode(2);
        head.next.next.next = new ListNode(3);
        fromHeadtoTailPrintLinkedListByStack(head);
        System.out.println();
        fromHeadtoTailPrintLinkedListByRecursion(head);

    }
}

7 重建二叉树

1570765755613

根据前序和中序遍历,可以确定每颗子树根节点所在的位置,然后根据根节点,划分左右子树,之后再分别在左右子树中重复之前的划分过程。(递归实现)

public static Node constructBinaryTreeByPreInOrder(int[] preOrder, int[] inOrder, int preOrder_start,
                                                   int inOrder_start, int length) {
    if (length == 0) {
        return null;
    }

    int rootInOrderIndex = 0;
    for (int i = inOrder_start; i < inOrder_start + length; i++) {
        if (preOrder[preOrder_start] == inOrder[i]) {
            rootInOrderIndex = i;
            break;
        }
    }
    int left_length = rootInOrderIndex - inOrder_start;
    int right_length = length - left_length - 1;
    //根节点
    Node root = new Node(preOrder[preOrder_start]);
    //构建左子树
    root.left = constructBinaryTreeByPreInOrder(preOrder, inOrder, preOrder_start + 1,
                                                inOrder_start, left_length);
    //构建右子树
    root.right = constructBinaryTreeByPreInOrder(preOrder, inOrder, preOrder_start + left_length + 1,
                                                 rootInOrderIndex + 1, right_length);
    return root;

}

8 二叉树的下一个节点

1571056951976

分三种情况:

  1. 当前节点有右子树,下一个节点是右子树中最左的节点
  2. 无右子树
    1. 父节点的左孩子是当前节点,下一个节点是父节点
    2. 遍历该节点的父节点,直到父节点的左孩子是当前节点,下一个节点是父节点
public class TreeNode {
    public int value;
    public TreeNode left;
    public TreeNode right;
    public TreeNode parent;

    public TreeNode(int value) {
        this.value = value;
    }
}

public static TreeNode findNextNode(TreeNode treeNode) {
    //当前节点有右子树,下一个节点是右子树中最左的节点
    if (treeNode.right != null) {
        TreeNode cur = treeNode.right;
        while (cur.left != null) {
            cur = cur.left;
        }
        return cur;
    } else {
        //无右子树
        TreeNode par = treeNode.parent;
        //父节点的左孩子是当前节点,下一个节点是父节点
        if (par.left == treeNode) {
            return par;
        } else {
            //遍历该节点的父节点,直到父节点的左孩子是当前节点,下一个节点是父节点
            while (par.left != treeNode) {
                par = par.parent;
                treeNode = treeNode.parent;
            }
            return par;
        }

    }

}

9 用两个栈实现队列

1571061821079

delteHead和getHead操作:只有stackPop为空时,才能往里面压入数据

import java.util.Stack;

public class TwoStackToQueue<T> {
    private Stack<T> stackPush;
    private Stack<T> stackPop;

    public TwoStackToQueue() {
        stackPush = new Stack<T>();
        stackPop = new Stack<T>();
    }

    public void appendTail(T node) {
        stackPush.push(node);
    }

    public T deleteHead() {
        if (stackPush.isEmpty() && stackPop.isEmpty()) {
            throw new RuntimeException("Queue is empty!");
        } else {
            if (stackPop.isEmpty()) {
                while (!stackPush.isEmpty()) {
                    stackPop.push((stackPush.pop()));
                }

            }

            return stackPop.pop();
        }
    }
    public T getHead(){
        if (stackPush.isEmpty() && stackPop.isEmpty()) {
            throw new RuntimeException("Queue is empty!");
        } else {
            if (stackPop.isEmpty()) {
                while (!stackPush.isEmpty()) {
                    stackPop.push((stackPush.pop()));
                }

            }

            return stackPop.peek();
        }

    }

    public static void main(String[] args) {
        TwoStackToQueue twoStackToQueue = new TwoStackToQueue();
        twoStackToQueue.appendTail(1);
        twoStackToQueue.appendTail(2);
        twoStackToQueue.appendTail(3);
        twoStackToQueue.appendTail(4);
        System.out.println(twoStackToQueue.deleteHead());
        System.out.println(twoStackToQueue.deleteHead());
        System.out.println(twoStackToQueue.deleteHead());
        twoStackToQueue.appendTail(5);
        System.out.println(twoStackToQueue.deleteHead());
        System.out.println(twoStackToQueue.getHead());
        System.out.println(twoStackToQueue.deleteHead());
        System.out.println(twoStackToQueue.deleteHead());

    }

}

用两个队列实现栈

引入队列queue1和queue2,每次pop操作,就将queue1中的节点都放入queue2中,直至queue1中的节点个数为1,然后再将queue1的节点poll,之后,再交换queue1和queue2中的值。peek操作类似。

import java.util.LinkedList;
import java.util.Queue;

public class TwoQueueToStack<T> {
    private Queue<T> queue1;
    private Queue<T> queue2;

    public TwoQueueToStack() {
        queue1 = new LinkedList<T>();
        queue2 = new LinkedList<T>();
    }

    public void push(T node) {
        queue1.add(node);

    }

    public T pop() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            throw new RuntimeException("the stack is empty!");
        }

        while (queue1.size() != 1) {
            queue2.add(queue1.poll());
        }
        T node = queue1.poll();
        Queue<T> queue = queue1;
        queue1 = queue2;
        queue2 = queue;
        return node;
    }

    public T peek() {
        if (queue1.isEmpty() && queue2.isEmpty()) {
            throw new RuntimeException("the stack is empty!");
        }
        T node = null;
        while (!queue1.isEmpty()) {
            node = queue1.poll();
            queue2.add(node);
        }

        Queue<T> queue = queue1;
        queue1 = queue2;
        queue2 = queue;

        return node;

    }

    public static void main(String[] args) {
        TwoQueueToStack twoQueueToStack = new TwoQueueToStack();
        twoQueueToStack.push(1);
        twoQueueToStack.push(2);
        twoQueueToStack.push(3);
        System.out.println(twoQueueToStack.pop());
        twoQueueToStack.push(4);
        System.out.println(twoQueueToStack.peek());
        System.out.println(twoQueueToStack.pop());
        System.out.println(twoQueueToStack.pop());
        System.out.println(twoQueueToStack.pop());
        System.out.println(twoQueueToStack.pop());

    }

}

10 斐波那契数列

1571104833926

1.递归,时间复杂度O(2^n^)

2.循环,时间复杂度O(n

public class Fibonacci_10 {
    public static int calFibonacciRecursive(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }

        return calFibonacciRecursive(n - 1) + calFibonacciRecursive(n - 2);
    }

    public static int calFibonacciNoRecursive(int n) {
        int[] res={0,1};
        if(n<2){
            return res[n];
        }
        int f1=0;
        int f2=1;
        int f=0;
        for (int i = 2; i <=n; i++) {
            f=f1+f2;
            f1=f2;
            f2=f;
        }

        return f;

    }

    public static void main(String[] args) {
        System.out.println(calFibonacciRecursive(10));
        System.out.println(calFibonacciNoRecursive(20));

    }
}

应用

1571104927352

1571104947040

11 旋转数组的最小数字

1571129856042

解法:

  1. 暴力,时间复杂度O(n)
  2. 二分查找,时间复杂度O(logn)。使用两个指针p1,p2,然后根据计算的mid值来移动p1,p2。
  3. 当arr[p1]== arr[mid] == arr[p2]时,无法判断p1和p2属于哪个递增子数组,直接调用getMin,进行顺序查找。
  4. 当arr[mid]>=arr[p1]时,mid属于p1所在的递增子数组,令p1=mid,继续二分。
  5. 当arr[mid]<=arr[p2]时,处理过程和2类似。

三种输入情况:

  1. 1,2,3,4,5
  2. 3,4,5,1,2
  3. 1,1,1,0,1
import org.jetbrains.annotations.NotNull;

public class FindMinNumberInRotateArray_11 {
    public static int findByDichotomy(int[] arr) {
        if (arr[0] < arr[arr.length - 1]) {
            return arr[0];
        }
        int p1 = 0;
        int p2 = arr.length - 1;
        int mid = 0;
        int min = arr[0];
        while (arr[p1] >= arr[p2]) {
            if (p1 + 1 == p2) {
                min = arr[p2];
                break;
            }

            mid = p1 + ((p2 - p1) >> 1);

            if (arr[mid] == arr[p1] && arr[mid] == arr[p2]) {
                return getMin(arr, p1, p2);

            }
            if (arr[mid] >= arr[p1]) {
                p1 = mid;

            } else if (arr[mid] <= arr[p2]) {
                p2 = mid;
            }
        }
        return min;

    }

    private static int getMin(int[] arr, int p1, int p2) {
        int min = arr[p1];
        for (int i = p1 + 1; i < p2; i++) {
            if (min > arr[i]) {
                min = arr[i];
            }
        }
        return min;
    }

    public static int findByForce(int[] arr) {
        int min = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (min > arr[i]) {
                min = arr[i];
            }
        }
        return min;
    }

    public static void main(String[] args) {
        int[] arr = {3, 4, 5, 1, 2};
        int[] arr1 = {1, 2, 3, 4, 5};
        int[] arr2 = {1, 0, 1, 1, 1};
        int[] arr3 = {1};
        System.out.println(comparator(arr));
        System.out.println(comparator(arr1));
        System.out.println(comparator(arr2));
        System.out.println(comparator(arr3));

    }

    @NotNull
    public static String comparator(int[] arr) {
        return findByDichotomy(arr) == findByForce(arr) ? "true" : "false";
    }

}

12 矩阵中的路径

1571393951935

思路:

回溯法,字符的遍历过程如下所示

1571394792073

public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
    if (matrix == null || str == null || rows <= 0 || cols <= 0) {
        return false;
    }

    //标记数组,用来记录该字符是否访问过
    boolean[][] mark = new boolean[rows][cols];
    char[][] chars = toArray(matrix, rows, cols);

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (process(chars, str, 0, mark, i, j)) {
                return true;
            }
        }
    }

    return false;
}

//将一维数组转换成二维数组
public char[][] toArray(char[] matrix, int rows, int cols) {
    char[][] chars = new char[rows][cols];
    for (int i = 0, index = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            chars[i][j] = matrix[index++];
        }

    }
    return chars;

}

//递归函数
public boolean process(char[][] chars, char[] str, int pathLength, boolean[][] mark, int row, int column) {
    //遍历的路径长度和字符串长度相等,说明,之前的字符都已经成功匹配,返回true
    if (pathLength == str.length) {
        return true;
    }
    //数组下标越界、字符不匹配、字符已经访问过,都返回false
    if (row < 0 || column < 0 || row >= chars.length || column >= chars[0].length
        || chars[row][column] != str[pathLength] || mark[row][column]) {
        return false;
    }
    //字符已访问,标记为true
    mark[row][column] = true;
    //递归遍历该字符傍边的字符,匹配成功,则路径长度加1
    if (process(chars, str, pathLength + 1, mark, row - 1, column) ||
        process(chars, str, pathLength + 1, mark, row + 1, column) ||
        process(chars, str, pathLength + 1, mark, row, column - 1) ||
        process(chars, str, pathLength + 1, mark, row, column + 1)) {
        return true;
    }
    //该字符旁边的字符都不匹配,则说明这条路不符合,还原,将字符的遍历标记设置为false
    mark[row][column] = false;
    return false;

}

13 机器人的运动范围

1571394884126

思路:图的深度优先遍历

public int movingCount(int threshold, int rows, int cols) {
    //标记数组
    boolean[][] mark = new boolean[rows][cols];
    //存储每个位置的数位和
    int[][] matrix = new int[rows][cols];
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = getValue(i) + getValue(j);
        }
    }

    return process(threshold, matrix, mark, 0, 0, rows, cols);

}

private int process(int threshold, int[][] matrix, boolean[][] mark, int i, int j, int rows, int cols) {
    int count = 0;
    //递归终止条件
    if (i < 0 || j < 0 || i >= rows || j >= cols || matrix[i][j] > threshold || mark[i][j]) {
        return 0;
    }
    //将访问过的位置标记为true
    mark[i][j] = true;
    //访问当前位置,加1,然后继续遍历该位置傍边的位置,累加起来,最终的返回值就是所能到达的格子数
    count = 1 + process(threshold, matrix, mark, i - 1, j, rows, cols) + process(threshold, matrix, mark, i + 1, j, rows, cols) +
        process(threshold, matrix, mark, i, j - 1, rows, cols) + process(threshold, matrix, mark, i, j + 1, rows, cols);
    return count;

}

//计算一个整数的数位之和
public int getValue(int num) {
    int res = 0;
    int tmp = 0;
    while (num / 10 > 0) {
        tmp = num / 10;
        res += num - tmp * 10;
        num = tmp;
    }
    res += num;
    return res;

}

14 剪绳子

1571404008081

public class cutRope_14 {
    //      思路:
    //      f(n)=max(f(i)*f(n-i)),0<i<n
    //      f(n)表示把绳子剪成若干段后各段乘积的最大值
    //1.递归
    public int cutRope(int target) {

        if (target < 2) {
            return 0;
        }
        if (target == 2) {
            return 1;
        }
        if (target == 3) {
            return 2;
        }

        int max = 0;

        for (int i = 1; i <= (target - 1) / 2; i++) {
            max = Math.max(max, process(i) * process(target - i));
        }

        return max;
    }

    public int process(int target) {
        //递归终止条件
        if (target < 4) {
            return target;
        }
        int max = 0;
        for (int i = 1; i <= (target - 1) / 2; i++) {

            max = Math.max(max, process(i) * process(target - i));
        }

        return max;

    }

    //2.动态规划,时间复杂度O(n^2),由递归转化而来
    public int cutRopeDP(int target) {
        if (target < 2) {
            return 0;
        }
        if (target == 2) {
            return 1;
        }
        if (target == 3) {
            return 2;
        }
        int[] dp = new int[target + 1];
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        for (int i = 4; i <= target; i++) {
            for (int j = 1; j <= i / 2; j++) {

                dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
            }
        }
        return dp[target];

    }

    //    3.贪心,时间复杂度O(1)
    //    n>4时,划分出尽可能多的3,因为3(n-3)>=2(n-2)
    //    n=4时,2*2 > 3*1,所以当划分出1和3时,要转变成2和2
    //    n<4时,特殊情况,单独处理
    public int cutRopeGreedy(int target) {
        if (target < 2) {
            return 0;
        }
        if (target == 2) {
            return 1;
        }
        if (target == 3) {
            return 2;
        }
        int timeOf3 = target / 3;
        if (target - timeOf3 * 3 == 1) {
            timeOf3--;
        }

        int timeOf2 = (target - timeOf3 * 3) / 2;
        int res = (int) (Math.pow(3, timeOf3) * Math.pow(2, timeOf2));
        return res;

    }

    //测试
    public static void main(String[] args) {
        cutRope_14 cutRope_14 = new cutRope_14();
        System.out.println(cutRope_14.cutRope(14));
        System.out.println(cutRope_14.cutRopeDP(14));
        System.out.println(cutRope_14.cutRopeGreedy(14));
    }

}

15 二进制中1的个数

1571829733900

public class BinaryNumber_15 {
    public int NumberOf1(int n) {
        //数字在计算机中以二进制形式存储,负数在计算机中以补码存储,int类型的数据占4个字节
        //为了防止负数右移出现死循环的情况,可以把1每次左移一位,然后和n比较
        int res = 0;
        int flag = 1;
        while (flag != 0) {
            if ((n & flag) != 0) {
                res++;
            }
            flag = flag << 1;
        }
        return res;
    }

    public int NumberOf1Improve(int n) {
        //(n-1)&n 每次运算的结果将n中二进制表示最右边的1变为0
        int res = 0;
        while (n != 0) {
            n=(n-1)&n;
            res++;
        }
        return res;
    }

    public static void main(String[] args) {
        BinaryNumber_15 binaryNumber_15 = new BinaryNumber_15();
        int res = binaryNumber_15.NumberOf1(-8);
        System.out.println(res);

    }
}

16 数值的整数次方

1571831242479

  • base=0,exponent<0是非法输入,给用户提示输入错误

  • 提高运算效率:

1571831788488

public class NumberExponent_16 {
    public double Power(double base, int exponent) {
        //非法输入
        if (base == 0 && exponent < 0) {
            throw new RuntimeException("input number error!");
        }
        double res = 1;
        double tmp = exponent;
        if (exponent < 0) {
            exponent = -exponent;
        }
        //O(n)
        for (int i = 1; i <= exponent; i++) {
            res = res * base;
        }
        if (tmp < 0) {
            res = 1 / res;
        }

        return res;

    }

    public double PowerImprove(double base, int exponent) {
        //非法输入
        if (base == 0 && exponent < 0) {
            throw new RuntimeException("input number error!");
        }
        double res = 1;
        double tmp = exponent;
        if (exponent < 0) {
            exponent = -exponent;
        }

        if (exponent % 2 == 0) {
            //O(n/2)
            for (int i = 1; i <= exponent / 2; i++) {
                res = res * base;
            }
            res = res * res;
        } else {
            for (int i = 1; i <= (exponent - 1) / 2; i++) {
                res = res * base;
            }
            res = res * res * base;
        }

        if (tmp < 0) {
            res = 1 / res;
        }

        return res;

    }
}

17 打印从1到最大的n位数

1571835029514

本质上是0-9的全排列顺序输出问题,用递归实现。

大数问题,一般使用字符串来表示数字。

public class PrintMaxNumber_17 {
    //n没有限定范围,大数问题,需要用字符串来表示
    public void print(int n) {
        if (n <= 0) {
            throw new RuntimeException("error input!");
        }
        char[] nums=new char[n];
        for (int i = 0; i < 10; i++) {
            //数字转字符,'0' + i 是 i 的ascii码
            nums[0]=(char)('0'+i);
            process(nums,0,n);
        }
    }

    public void process(char[] nums,int index,int len){
        if(index==len-1){
            print(nums);
            return;
        }
        for (int i = 0; i < 10; i++) {
            nums[index+1]=(char)('0'+i);
            process(nums,index+1,len);
        }

    }

    private void print(char[] nums) {
        //标记位,用来判断数字0之前是否有非零数字出现过
        int flag=0;
        String str="";
        for (int i = 0; i < nums.length; i++) {
            if(nums[i]!='0'){
                flag=1;
                str+=nums[i];
            }
            if(nums[i]=='0'&&flag==1){
                str+=nums[i];
            }
        }
        System.out.print(str+" ");
    }

    public static void main(String[] args) {
        PrintMaxNumber_17 printMaxNumber_17=new PrintMaxNumber_17();
        printMaxNumber_17.print(3);

    }
}

18 删除链表中的节点

image-20191025143654118

链表中删除节点的两种方法:image-20191025143901080

public class DeleteNode_18 {
    static class Node {
        int value;
        Node next;

        public Node(int value) {
            this.value = value;
        }

    }

    //O(1)
    public static Node deleteNode(Node head, Node deleteNode) {
        //要删除节点的下一个节点不为空时,用下一个节点的值替代当前节点,然后将当前节点指向下一个节点的节点,O(1)
        if (deleteNode.next != null) {
            deleteNode.value = deleteNode.next.value;
            deleteNode.next = deleteNode.next.next;
        } else {
            //链表中只有一个节点
            if (head == deleteNode) {
                head = null;
            } else {
                //要删除节点的下一个节点为空,即链表中最后一个节点,O(n)
                Node cur = head;
                while (cur.next != deleteNode) {
                    cur = cur.next;
                }
                cur.next = null;
            }
        }

        return head;

    }

    public static void printNode(Node head) {
        while (head != null) {
            System.out.print(head.value + " ");
            head = head.next;
        }
    }

    public static void main(String[] args) {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        node1.next = node2;
        node2.next = node3;
        Node head = deleteNode(node1, node3);

        printNode(head);

    }

}

19 正则表达式匹配

image-20191027134347823

image-20191027145920030

当模式中的第二个字符不是 “*” 时:

1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。

2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回 false。

而当模式中的第二个字符是 “*” 时:

如果字符串第一个字符跟模式第一个字符不匹配,则模式后移 2 个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有 3 种匹配方式:

1、模式后移 2 字符,相当于 x * 被忽略;

2、字符串后移 1 字符,模式后移 2 字符;

3、字符串后移 1 字符,模式不变,即继续匹配字符下一位,因为 * 可以匹配多位;

这里需要注意的是:Java 里,要时刻检验数组是否越界。

public class RegularExpressionMatch_19 {

    public boolean match(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        int strIndex = 0;
        int patternIndex = 0;
        return matchCore(str, strIndex, pattern, patternIndex);
    }

    public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
        //有效性检验:str到尾,pattern到尾,匹配成功
        if (strIndex == str.length && patternIndex == pattern.length) {
            return true;
        }
        //pattern先到尾,匹配失败
        if (strIndex != str.length && patternIndex == pattern.length) {
            return false;
        }
        //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位
        if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
            if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
                    || strIndex != str.length && (pattern[patternIndex] == '.')) {
                return matchCore(str, strIndex, pattern, patternIndex + 2)// 模式后移2,视为x*匹配0个字符
                        || matchCore(str, strIndex + 1, pattern, patternIndex + 2)// 视为模式匹配1个字符
                        || matchCore(str, strIndex + 1, pattern, patternIndex);// *匹配1个,再匹配str中的下一个
            } else {
                return matchCore(str, strIndex, pattern, patternIndex + 2);
            }
        }
        //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
        if ((strIndex != str.length && pattern[patternIndex] == str[strIndex])
                || (strIndex != str.length && pattern[patternIndex] == '.')) {
            return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
        }

        return false;
    }

    public static void main(String[] args) {
        RegularExpressionMatch_19 regularExpressionMatch_19 = new RegularExpressionMatch_19();
        boolean res = regularExpressionMatch_19.match("".toCharArray(), ".*".toCharArray());
        System.out.println(res);
    }

}

20 表示数值的字符串

image-20191027150227684

使用正则表达式进行匹配

[] : 字符集合
() : 分组
? : 重复 0 ~ 1 次

+ : 重复 1 ~ n 次

* : 重复 0 ~ n 次
. : 任意字符
\\. : 转义后的 .
\\d : 数字

import java.util.ArrayList;
import java.util.List;

public class JudgeNumber_20 {
    public boolean judge(String str) {
        if (str == null || str.length() == 0)
            return false;
        return str.matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");

    }

    public static void main(String[] args) {
        JudgeNumber_20 judgeNumber_20 = new JudgeNumber_20();
        String[] strings = {"+100", "5e2", "-123", "3.1416", "-1E-16",
                "12e", "1a3.14", "1.2.3", "+-5", "12e+4.3" };
        for (String str : strings) {
            System.out.println(str + " " + judgeNumber_20.judge(str));
        }

    }

}

21 调整数组顺序使奇数位于偶数前面

image-20191101211116362

使用双指针begin和end

begin=0,end=arr.length-1

public class OddEvenNumber_21 {
    public void adjust(int[] arr) {
        int begin = 0;
        int end = arr.length - 1;
        while (begin < end) {
            while (judgeOddEven(arr[begin]) && begin < end) {
                begin++;
            }
            while (!judgeOddEven(arr[begin]) && begin < end) {
                end--;
            }
            swap(arr, begin, end);

        }
    }
    
    public boolean judgeOddEven(int i) {
        return i % 2 == 0;
    }

    public void swap(int[] arr, int begin, int end) {
        int tmp = arr[begin];
        arr[begin] = arr[end];
        arr[end] = tmp;
    }

    public void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        OddEvenNumber_21 oddEvenNumber_21 = new OddEvenNumber_21();
        int[] arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * 20);
        }
        oddEvenNumber_21.printArray(arr);
        oddEvenNumber_21.adjust(arr);
        oddEvenNumber_21.printArray(arr);

    }
}

22 链表中倒数第k个节点

image-20191101221249509

设置两个指针p1、p2,p1=p2=head

让p1先走k-1步,然后p1和p2同时走,p1走到链表尾结点,则p2正好走到倒数第k个节点

代码鲁棒性:

  1. 输入的链表头指针为null
  2. k=0
  3. 链表中节点个数小于k
public class TheLastKthNode_22 {
    static class ListNode {
        int value;
        ListNode next;

        ListNode(int value) {
            this.value = value;
        }
    }

    public static ListNode theLastKthNode(ListNode head, int k) {
        if(head==null){
            throw new RuntimeException("Error,head is null!");
        }
        if(k==0){
            throw new RuntimeException("Error,the value of k is 0!");
        }
        ListNode p1 = head;
        ListNode p2 = head;
        int i=0;
        while (i != (k - 1)) {
            if(p1.next==null){
                throw new RuntimeException("Error,the number of ListNode is less than k!");
            }
            p1 = p1.next;
            i++;
        }


        while (p1.next != null) {
            p1 = p1.next;
            p2 = p2.next;
        }

        return p2;

    }

    public static void main(String[] args) {
        ListNode listNode1 = new ListNode(1);
        ListNode listNode2 = new ListNode(2);
        ListNode listNode3 = new ListNode(3);
        ListNode listNode4 = new ListNode(4);
        ListNode listNode5 = new ListNode(5);
        ListNode listNode6 = new ListNode(6);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        listNode5.next = listNode6;

        ListNode listNode=theLastKthNode(listNode1,8);
        System.out.println(listNode.value);

    }

}

23 链表中环的入口节点

image-20191101221924959

思路:

  1. 先判断链表是否存在环,使用快慢指针,快指针一次走两步,慢指针一次走一步,两个指针相遇,则说明链表有环,记录下相遇时候的节点LoopNode
  2. 计算环中的节点个数,从LoopNode节点出发,再次回到LoopNode,就得到了环中节点的个数k
  3. 设置两个指针p1和p2,让p1先走k步,然后p1和p2同时走,相遇时候的节点EntryNode即为环的入口节点
public class LoopOfLinkedList_23 {
    static class LinkedList {
        int value;
        LinkedList next;

        LinkedList(int value) {
            this.value = value;
        }
    }

    public static LinkedList findLoopNode(LinkedList head) {
        if (head == null) {
            throw new RuntimeException("head is null!");
        }
        LinkedList p1 = head.next;
        LinkedList p2 = head;
        LinkedList loopNode = null;

        while (p1.next != null) {

            p1 = p1.next;
            if (p1.next != null) {
                p1 = p1.next;
            }
            p2 = p2.next;
//            System.out.println(p1.value + " " + p2.value);
            if (p1 == p2) {
                loopNode = p1;
                break;
            }
        }
        if (loopNode == null) {
            return null;
        }

        int count = 1;
        LinkedList tmpList=loopNode;
        while (loopNode.next != tmpList) {
            count++;
//            System.out.println(count);
            loopNode = loopNode.next;
        }

        p1 = p2 = head;

        while (count-- > 0) {
            p1 = p1.next;
        }
        while (p1 != p2) {
            p1 = p1.next;
            p2 = p2.next;
        }

        return p1;

    }

    public static void main(String[] args) {
        LinkedList linkedList1 = new LinkedList(1);
        LinkedList linkedList2 = new LinkedList(2);
        LinkedList linkedList3 = new LinkedList(3);
        LinkedList linkedList4 = new LinkedList(4);
        LinkedList linkedList5 = new LinkedList(5);
        LinkedList linkedList6 = new LinkedList(6);
        linkedList1.next = linkedList2;
        linkedList2.next = linkedList3;
        linkedList3.next = linkedList4;
        linkedList4.next = linkedList5;
        linkedList5.next = linkedList6;
        linkedList6.next = linkedList3;

        LinkedList linkedList = findLoopNode(linkedList1);
        System.out.println(linkedList.value);

    }

}

24 反转链表

image-20191106191908176

  • 非递归:使用一个newList节点来记录逆向之后的头结点
  • 递归:每次递归,head.next要设置为null
public class ReverseLinkedList {
    static class LinkedList {
        int value;
        LinkedList next;

        LinkedList(int value) {
            this.value = value;
        }

    }
    //非递归
    public static LinkedList reverse(LinkedList head) {
        LinkedList newList = new LinkedList(-1);
        while (head != null) {
            LinkedList next = head.next;
            head.next = newList.next;
            newList.next = head;
            head = next;
        }
        return newList.next;
    }
    //递归
    public static LinkedList reverseByRecursive(LinkedList head){
        if(head==null||head.next==null){
            return head;
        }
        LinkedList next=head.next;
        head.next=null;
        LinkedList newHead=reverseByRecursive(next);
        next.next=head;
        return newHead;


    }


    public static void main(String[] args) {
        LinkedList linkedList1 = new LinkedList(1);
        LinkedList linkedList2 = new LinkedList(2);
        LinkedList linkedList3 = new LinkedList(3);
        LinkedList linkedList4 = new LinkedList(4);
        LinkedList linkedList5 = new LinkedList(5);
        LinkedList linkedList6 = new LinkedList(6);
        linkedList1.next = linkedList2;
        linkedList2.next = linkedList3;
        linkedList3.next = linkedList4;
        linkedList4.next = linkedList5;
        linkedList5.next = linkedList6;
        LinkedList reverseHead = reverse(linkedList1);
        while (reverseHead!=null){
            System.out.println(reverseHead.value);
            reverseHead=reverseHead.next;
        }
    }

}

25 合并两个排序的链表

image-20191106202817107

public class MergeSortedLinkedList_25 {
    public static LinkedList mergeSortedLinkedList(LinkedList head1, LinkedList head2) {

        LinkedList head = new LinkedList(-1);
        LinkedList cur = head;
        while (head1 != null && head2 != null) {
            if (head1.value <= head2.value) {
                cur.next = head1;
                head1 = head1.next;
            } else {
                cur.next = head2;
                head2 = head2.next;
            }
            cur = cur.next;
        }
        if (head1 != null) {
            cur.next = head1;
        }
        if (head2 != null) {
            cur.next = head2;
        }
        return head.next;

    }

    public static LinkedList mergeSortedLinkedListByRecursive(LinkedList head1, LinkedList head2) {
        if (head1 == null) {
            return head2;
        }
        if (head2 == null) {
            return head1;
        }

        if (head1.value <= head2.value) {
            head1.next = mergeSortedLinkedListByRecursive(head1.next, head2);
            return head1;
        } else {
            head2.next = mergeSortedLinkedListByRecursive(head1, head2.next);
            return head2;
        }

    }
}

26 树的子结构

image-20191106213518317

public class SubTree_26 {
    static class BinaryTreeNode {
        double value;
        BinaryTreeNode left;
        BinaryTreeNode right;

        BinaryTreeNode(int value) {
            this.value = value;
        }
    }

    //遍历所有根节点值相同的子树
    public static boolean hasSubTree(BinaryTreeNode binaryTreeNode1, BinaryTreeNode binaryTreeNode2) {
        boolean result = false;
        if (binaryTreeNode1 != null && binaryTreeNode2 != null) {
            if (binaryTreeNode1.value == binaryTreeNode2.value) {
                result = judge(binaryTreeNode1, binaryTreeNode2);
            }
            if (!result) {
                result = hasSubTree(binaryTreeNode1.left, binaryTreeNode2);
            }

            if (!result) {
                result = hasSubTree(binaryTreeNode1.right, binaryTreeNode2);
            }

        }
        return result;

    }

    //判断根节点相同的子树是否完全一样
    public static boolean judge(BinaryTreeNode binaryTreeNode1, BinaryTreeNode binaryTreeNode2) {
        if (binaryTreeNode2 == null) {
            return true;
        }
        if (binaryTreeNode1 == null) {
            return false;
        }

        if (!Equals(binaryTreeNode1.value, binaryTreeNode2.value)) {
            return false;
        } else {
            return judge(binaryTreeNode1.left, binaryTreeNode2.left) && judge(binaryTreeNode1.right, binaryTreeNode2.right);
        }

    }
    //计算机表示小数(float、double)存在误差,不能直接用等号判断两个小数是否相等。如果两个小数的差的绝对值很小,
    // 小于0.0000001,则认为相等
    public static boolean Equals(double a, double b) {
        if (Math.abs(a - b) < 0.0000001) {
            return true;
        } else {
            return false;
        }
    }

}

27 二叉树的镜像

image-20191124162537024

image-20191124162549574

前序遍历,递归依次交换左右节点

public class BinaryTreeMirror_27 {
    class BinaryTreeNode {
        int value;
        BinaryTreeNode left;
        BinaryTreeNode right;
    }

    public void mirrorRecursive(BinaryTreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            return;
        }
        BinaryTreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        if (root.left != null) {
            mirrorRecursive(root.left);
        }
        if (root.right != null) {
            mirrorRecursive(root.right);
        }
    }
}

28 对称的二叉树

image-20191124163739080

image-20191124163758444

思路:比较二叉树的根左右和根右左遍历的序列,来进行判断

public class BinaryTreeSymmetry_28 {
    class BinaryTreeNode {
        int value;
        BinaryTreeNode left;
        BinaryTreeNode right;
    }

    public boolean isSymmetry(BinaryTreeNode root) {

        return process(root, root);
    }

    public boolean process(BinaryTreeNode root1, BinaryTreeNode root2) {
        if (root1 == null && root2 == null) {
            return true;
        }
        if (root1 == null || root2 == null) {
            return false;
        }
        if (root1.value != root2.value) {
            return false;
        }

        return process(root1.left, root2.right) && process(root1.right, root2.left);

    }

}

29 顺时针打印矩阵

image-20191124165235771

每次打印一个圈,可以用递归或者循环实现

public class ClockwisePrintMatrix_29 {
    public static void clockwisePrintMatrix(int[][] arr) {
        if (arr == null) {
            return;
        }

        print(arr, 0, 0, arr.length - 1, arr[0].length - 1);

    }

    public static void print(int[][] arr, int leftX, int leftY, int rightX, int rightY) {
        //递归终止条件
        if (leftX > rightX || leftY > rightY) {
            return;
        }
        //单行和单列需要单独处理,否则会输出重复的序列
        if (leftX == rightX) {
            for (int i = leftY; i <= rightY; i++) {
                System.out.print(arr[leftX][i] + " ");
            }
        } else if (leftY == rightY) {
            for (int i = leftX; i <= rightX; i++) {
                System.out.print(arr[i][leftY] + " ");
            }
            //其他情况,顺时针转圈打印,注意边界的处理
        } else {

            for (int i = leftY; i < rightY; i++) {
                System.out.print(arr[leftX][i] + " ");
            }
            for (int i = leftX; i <= rightX; i++) {
                System.out.print(arr[i][rightY] + " ");

            }
            for (int i = rightY - 1; i >= leftY; i--) {
                System.out.print(arr[rightX][i] + " ");
            }

            for (int i = rightX - 1; i > leftX; i--) {
                System.out.print(arr[i][leftY] + " ");

            }

            print(arr, ++leftX, ++leftY, --rightX, --rightY);
        }

    }

    public static void main(String[] args) {
        int[][] arr = {
                {1, 2, 3, 4},
                {5, 6, 7, 8},
                {9, 10, 11, 12},
                {13, 14, 15, 16}

        };
        int[][] arr1 = {
                {1, 2, 3, 4},
        };

        int[][] arr2 = {
                {1}, {2},{3}, {4},
        };
        clockwisePrintMatrix(arr);
        System.out.println();
        clockwisePrintMatrix(arr1);
        System.out.println();
        clockwisePrintMatrix(arr2);

        /* result:
        * 1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10
        * 1 2 3 4
        * 1 2 3 4
        * */
    }
}

30 包含min函数的栈

image-20191124215614501

使用两个栈:dataStack和minStack。

  • dataStack存储实际的数据

  • minStack存储当前栈内元素最小的数据

image-20191124220023053

import java.util.Stack;

public class MinStack {
    Stack<Integer> dataStack;
    Stack<Integer> minStack;

    public MinStack() {
        dataStack = new Stack<>();
        minStack = new Stack<>();
    }

    public void push(int data) {
        dataStack.push(data);
        if (minStack.isEmpty()) {
            minStack.push(data);
        } else {
            int min = minStack.peek();
            if (data < min) {
                min = data;
            }
            minStack.push(min);
        }

    }

    public int pop() {
        int data = dataStack.pop();
        minStack.pop();
        return data;
    }

    public int min() {
        return minStack.peek();

    }

    public static void main(String[] args) {
        MinStack minStack = new MinStack();
        minStack.push(3);
        System.out.println(minStack.min());
        minStack.push(4);
        System.out.println(minStack.min());
        minStack.push(2);
        System.out.println(minStack.min());
        minStack.push(1);
        System.out.println(minStack.min());
        minStack.pop();
        System.out.println(minStack.min());

    }

}

猜你喜欢

转载自www.cnblogs.com/dockerchen/p/11924939.html