剑指offer题目解析(附带视频解析链接)

旧时模样 提交于 2020-01-29 16:57:29

写在前面

代码说明:代码的下载地址: https://github.com/WuNianLuoMeng/Coding
视频说明:第一次以这样的形式录视频,如果有哪里说的不对,还请各位及时指出,谢谢~~~

二维数组中的查找 视频链接

方法一:

通过遍历array数组,去查找array数组中有没有target的值。它的时间复杂度是(O(n * m))

    public boolean Find(int target, int [][] array) {
        for (int i = 0; i < array.length; i++)
        {
            for (int j = 0; j < array[0].length; j++)
            {
                if (array[i][j] == target)
                    return true;
            }
        }
        return false;
    }

方法二(从矩阵的右上角开始找):

设置一个i,j表示所找当前位置,如果说array[i][j] > target大的话,接着就往左找,反之往下找,直到找到array[i][j] == target为止。它的时间复杂度是:O(n + m)

public boolean Find(int target, int [][] array) {
        int i = 0;
        int j = array[0].length - 1;
        while(i >= 0 && i < array.length && j >= 0 && j < array[0].length)
        {
            // array[i][j]
            if (array[i][j] == target)
                return true;
            else if (array[i][j] > target)
                j--;
            else
                i++;
        }
        return false;
    }

方法三(从矩阵的左下角开始找):

设置一个i,j表示所找当前位置,如果说array[i][j] > target大的话,接着就往上找,反之往右找,直到找到array[i][j] == target为止。它的时间复杂度是:O(n + m)

public boolean Find(int target, int [][] array) {
        int i = array.length - 1;
        int j = 0;
        while(i >= 0 && i < array.length && j >= 0 && j < array[0].length)
        {
            // array[i][j]
            if (array[i][j] == target)
                return true;
            else if (array[i][j] > target)
                i--;
            else
                j++;
        }
        return false;
    }

替换空格 视频链接

方法一:去遍历字符串,然后判断当前位置的字符是否为空格,如果为空格的话,就追加"%20",如果不为空格的话,那么就追加当前位置的字符

public String replaceSpace(StringBuffer str) {
        int len = str.length();
        String res = "%20";
        StringBuffer ans = new StringBuffer();
        for (int i = 0; i < len; i++) {
            ans.append(str.charAt(i) == ' ' ? res : str.charAt(i));
        }
        return ans.toString();
    }

从尾到头打印链表 视频链接

方法一:通过Java中的Stack类去模拟栈的过程

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        Stack<Integer> stack = new Stack<>();
        while(listNode != null) {
            stack.add(listNode.val); /// 取当前节点的值放入栈中
            listNode = listNode.next; /// 更新当前节点为下一个节点
        }
        while (!stack.isEmpty()) {
            list.add(stack.pop()); /// 取出当前栈顶元素然后放入list中
        }
        return list;
    }

方法二:采用递归的方式去模拟链表反置的作用

private static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        if (listNode == null) {
            return list;
        }
        return solve(list, listNode);
    }
    // 1->2->3->4
    private static ArrayList<Integer> solve(ArrayList<Integer> list, ListNode listNode) {
        if (listNode.next != null) {   /// 当前节点的下一个节点不为空
            list = solve(list, listNode.next); /// 往下递归
        }
        list.add(listNode.val);
//        System.out.println(list);
        return list;
    }

重建二叉树 视频链接

方法一:通过依次遍历前序序列,然后在中序序列中确定当前遍历的前序序列中的数字所在的位置,然后在去划分出当前节点的左右子树,最后在去传入递归程序即可。

import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

public class Main4 {
    private static int index = 0;
    private static TreeNode solve(int[] pre, int[] tempIn) {
        int len1 = 0; /// 当前节点的左子树的节点的个数
        int len2 = 0; /// 当前节点的右子树的节点的个数
        for (int i = 0; i < tempIn.length; i++) {
            if (pre[index] == tempIn[i]) {
                break;
            }
            len1 ++; /// 左子树节点的个数++
        }
        len2 = tempIn.length - len1 - 1;

        int index1 = 0;
        int index2 = 0;
        int[] temp1 = new int[len1]; /// 当前节点的左子树
        int[] temp2 = new int[len2]; /// 当前节点的右子树
        boolean flag = false;
        for (int i = 0; i < tempIn.length; i++) {
            if (pre[index] == tempIn[i]) {
                flag = true;
            } else if (!flag) {
                temp1[index1++] = tempIn[i];
            } else {
                temp2[index2++] = tempIn[i];
            }
        }
        TreeNode node = new TreeNode(pre[index]);
        node.right = null;
        node.left = null;
//        System.out.printf("%d左子树:", pre[index]);
//        for (int i = 0; i < temp1.length; i++) {
//            System.out.printf("%d ", temp1[i]);
//        }
//        System.out.printf(",");
//        System.out.printf("%d右子树:", pre[index]);
//        for (int i = 0; i < temp2.length; i++) {
//            System.out.printf("%d ", temp2[i]);
//        }
//        System.out.println();
        if (index < pre.length && temp1.length > 0) {
            index++; /// 遍历前序序列的下标加1
            node.left = solve(pre, temp1); /// 创建当前节点的左子树
        }
        if (index < pre.length && temp2.length > 0) {
            index++; /// 遍历前序序列的下标加1
            node.right = solve(pre, temp2); /// 创建当前节点的右子树
        }
        return node;
    }
    private static TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        index = 0; /// 遍历前序序列的下标
        return solve(pre, in);
    }

    public static void main(String[] args) {
        int[] pre = {1, 2, 4, 7, 3, 5, 6, 8}; /// 前序遍历
        int[] in = {4, 7, 2, 1, 5, 3, 8, 6}; /// 中序遍历
        TreeNode root = reConstructBinaryTree(pre, in);

        dfs1(root);
        System.out.println();
        dfs2(root);
        System.out.println();
        dfs3(root);
        System.out.println();

    }
    private static void dfs1(TreeNode node) {
        System.out.printf("%d ", node.val);
        if (node.left != null) {
            dfs1(node.left);
        }
        if (node.right != null) {
            dfs1(node.right);
        }
    }
    private static void dfs3(TreeNode node) {
        if (node.left != null) {
            dfs3(node.left);
        }
        if (node.right != null) {
            dfs3(node.right);
        }
        System.out.printf("%d ", node.val);
    }

    private static void dfs2(TreeNode node) {
        if (node.left != null) {
            dfs2(node.left);
        }
        System.out.printf("%d ", node.val);
        if (node.right != null) {
            dfs2(node.right);
        }
    }

}

用两个栈去实现队列 视频链接

方法一:通过两个栈中元素之间的的复制交换去实现了队列的功能。

import java.util.Stack;

public class Main5 {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        while (!stack2.isEmpty()) {
            stack1.push(stack2.pop());  /// 将栈2中的元素放入到栈1中
        }
        stack1.push(node);
    }

    public int pop() {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop()); /// 将栈1中的元素放入到栈2中
        }
        return stack2.pop();
    }
}

旋转数组的最小数字 视频链接

方法一:遍历数组,不断去更新保存最小值的变量。时间复杂度是O(n)

    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) {
            return 0;
        }
        int ans = array[0];
        for (int i = 1; i < array.length; i++) {
            ans = Math.min(ans, array[i]);
        }
        return ans;
    }

方法二:通过二分的方法,不断去更新存在于两个子数组(两个非递减排序子数组)中的下标。时间复杂度是O(log(n))

 public int minNumberInRotateArray(int[] array) {
        if (array.length == 0) {
            return 0;
        }
        int l = 0;
        int r = array.length - 1;
        while (l < r - 1) {
            int mid = (l + r) >> 1;
            if (array[mid] >= array[l]) {
                l = mid; /// 说明mid所在的位置是在第一个非递减子数组中
            } else if (array[mid] <= array[r]) {
                r = mid; /// 说明mid所在的位置是在第二个非递减子数组中
            }
        }
        return array[r];
    }

斐波那契额数列 视频链接

方法一:采用递推的方式去求出a[n]的值

    public int Fibonacci(int n) {
        if (n == 0) {
            return 0;
        }
        int[] a = new int[n + 1];
        if (n == 1 || n == 2) {
            return 1;
        }
        a[1] = 1;
        a[2] = 1;
        for (int i = 3; i <= n; i++) {
            a[i] = a[i - 1] + a[i - 2];
        }
        return a[n];
    }

方法二:采用递归的方式去求出a[n]的值

public int Fibonacci(int n) {
        if (n == 0) {
            return 0; /// 终止递归的条件
        }
        if (n == 1 || n == 2) {
            return 1; /// 终止递归的条件
        }
        return Fibonacci(n - 1) + Fibonacci(n -2);
    }

跳台阶 视频链接

方法一:采用递归的方式去模拟出每次跳台阶的时候所作出的选择:要么跳1阶,要么跳2阶。

 public int JumpFloor(int target) {
        if (target == 1) {
            return 1; /// 目前递归到第1阶台阶,就没有必要往下去递归了
        }
        if (target == 2) {
            return 1 + JumpFloor(target - 1); /// 如果target == 2,其实就是等于从起点位置直接跳2阶 + 递归到第一阶的情况的总的跳阶的次数
//            return 2;
        }
        return JumpFloor(target - 1) + JumpFloor(target - 2); /// 当前target台阶的次数等于往前跳1阶加上往前跳2阶
    }

方法二:采用递推的方式去计算出从起点到第i阶的总的情况数与从起点到第i - 1阶的总的情况数和从起点到第i - 2阶的总的情况数之间的关系等式。

public int JumpFloor(int target) {
        if (target == 1) {
            return 1;
        }
        if (target == 2) {
            return 2;
        }
        int[] a = new int[target + 1]; /// a[i] 代表从起点到第i阶的总的情况数
        a[1] = 1; /// 第一阶的总情况数是1
        a[2] = 2; /// 第二阶的总情况数是2
        for (int i = 3; i <= target; i++) {
            a[i] = a[i - 1] + a[i - 2]; /// 对于第i阶的总情况数就等于从起点到第i-1阶的情况数(从0 -> i-1 -> +1 = i)加上从起点到第i-2阶的情况数(0 -> i-2 -> +2 ->i)
        }
        return a[target];
    }

变态跳台阶 视频链接

方法一:采用递推的方式,对于第i阶台阶的跳法的总次数,他是等于从第一阶到第i-1阶的情况数总和然后再加上从起点到终点的这一种情况数。

public int JumpFloorII(int target) {
        if (target == 1) {
            return 1;
        }
        int[] a = new int[target + 1];
        int sum = 1; /// 设置一个sum变量去记录1到n-1阶的总的情况数
        for (int i = 2; i <= target; i++) {
            a[i] = sum + 1; /// 对于第i阶台阶他是等于从第1阶到第i-1阶台阶的情况数之和然后再加上1(从起点到i阶的情况)
            sum = sum + a[i]; /// 需要去更新1到i阶的情况数
        }
        return a[target];
    }

矩阵覆盖 视频链接

方法一:对于2i的矩形,他的情况数就是等于2(i-1)的基础上在右边放置一个竖着的21的小矩阵,然后再加上2(i-2)的矩形的基础上在右边横着放置两个2*1的小矩阵。

public int RectCover(int target) {
        if (target == 0) {
            return 0;
        }
        if (target == 1) {
            return 1;
        }
        if (target == 2) {
            return 2;
        }
        int[] a = new int[target + 1];
        a[1] = 1;
        a[2] = 2;
        for (int i = 3; i <= target; i++) {
            a[i] = a[i - 1] + a[i - 2];  /// 对于2*i的矩形,他的情况数就是等于2*(i-1)的基础上在右边放置一个竖着的2*1的小矩阵,然后再加上2*(i-2)的矩形的基础上在右边横着放置两个2*1的小矩阵。
        }
        return a[target];
    }

二进制中1的个数

方法一:本质上就是对n的二进制表示中的每一位进行判断。

eg:
5 -》 101 & 1 —》 10 & 1 -》 1 & 1 -》 0 & 1这种方法是有问题的。
1 -> 0000000…01 -> (-1) -> 11…11111 -> 右移1位,数字-1的二进制的左边是补1的,也就是说,无论你右移多少次,结果都是-1.

改进:对n&运算的后面的那个数字进行操作:
5-》 101 & 1 -》 101 & 10 -》 101 & 100

    public int NumberOf1(int n) {
        int sum = 0; /// 记录1的个数
        int temp = 1; /// 本质上是用temp变量去判断n的每一位数字是否为1
        while (temp != 0) { /// 当temp为0的时候,说明已经移动了32次,然后就说明已经遍历完了n的每一位
            sum = (n & temp) != 0 ? sum + 1 : sum;
            temp = temp << 1;
        }
        return sum;
    }

方法二:本质上对n的二进制表示中的1的位置的判断。

eg:
5 -》 101 & 100(101 - 1) = 100 -》 100 & 011(100 - 1) = 000 -》 000

public int NumberOf1(int n) {
        int sum = 0; /// 记录1的个数
        while (n != 0) {  /// 说明当前n的二进制表示中肯定有1
            sum++;
            n = n & (n - 1); /// 本质上就是消除从右往左数的第一个位置的1。
        }
        return sum;
    }
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!