剑指Offer1—10题(leetcode 剑指Offer第二版)

本文探讨如何使用两个栈实现队列,二叉树中和为特定值的路径查找,数组中重复数字的定位,二维数组中的查找策略,字符串空格替换,从尾到头打印链表,重建二叉树,青蛙跳台阶问题,旋转数组的最小数字以及矩阵路径的搜索方法。涉及回溯法、数据结构优化及递归策略。
摘要由CSDN通过智能技术生成

1,用两个栈实现队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UBriU5i5-1649599445113)(%E5%89%91%E6%8C%87Offer.assets/1649074958156.png)]

import java.util.Stack;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: CQueue
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/4/4 20:08
 * 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
 * 示例 1:
 * 输入:
 * ["CQueue","appendTail","deleteHead","deleteHead"]
 * [[],[3],[],[]]
 * 输出:[null,null,3,-1]
 *
 */
public class CQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;

    public CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    /**
     * 队列尾部插入
     * @param value
     */
    public void appendTail(int value) {
        stack1.add(value);
    }

    /**
     * 队列头部删除
     * @return
     */
    public int deleteHead() {
        while (!stack1.isEmpty()) {
            stack2.push(stack1.pop());
        }
        int res = -1;
        if (!stack2.isEmpty()) {
            res = stack2.pop();
        }
        while (!stack2.isEmpty()) {
            stack1.push(stack2.pop());
        }
        return res;
    }

}

2,二叉树中和为某一值的路径

题目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LtiMAnCW-1649599445114)(%E5%89%91%E6%8C%87Offer.assets/1649122782118.png)]

思路分析

这道明显的是要求使用回溯法。进行所有的条件判断。

回溯法的步骤是:

  1. 先确定边界条件
  2. 进行之后要的操作
  3. 进行结束的判断

代码实现

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    LinkedList<LinkedList<Integer>> rest = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        LinkedList<Integer> list = new LinkedList<>();
		dfs(root, target, list);
        return res;
    }

    private void dfs(TreeNode root, int target, LinkedList<Integer> list) {
		//确定边界条件
        if(root == null) {
            return;
        }
        //进行接下的操作
        list.add(root.val);
        dfs(root.left, target - root.val, list);
        dfs(root.right, target - root.val, list);
        //结束的操作
        if(target == root.val && root.left == null && root.right == null) {
            //这里要new是因为如果直接填入的话后面改变list的时候都会影响到res,所以这里只能新建,然后把list放入
            res.add(new LinkedList<Integer> list);
        }
        list.removeLast();
    }
}

3,数组中重复的数字

题目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6oZrkVVQ-1649599445114)(%E5%89%91%E6%8C%87Offer.assets/1649250427640.png)]

思路分析

方法一:

因为没有要求不能改变数组,所以我们可以改变数组的排序方式,从而进行找到相同的值。这里将数组排序,那么相同的值必定在他的左边或者右边。

public class Solution1 {
    public int findRepeatNumber(int[] nums) {
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                return nums[i];
            }
        }
        return -1;
    }
}    

方法二:

在提到重复的时候,Java中的set可以防止重复,所以可以利用这个特点进行查找。我们在进行添加前先判断,如果这个值已经加入set中,就输出,否则就加入。

public class Solution1 {
    public int findRepeatNumber(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) {
                return nums[i];
            }else {
                set.add(nums[i]);
            }
        }
        return -1;
    }
} 

方法三:

既然要知道一个数有没有重复,那么我们完全可以创建一个Boolen类型的数组用于存放是否出现的结果。

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution1
 * @Description: 找出数组中重复的数字。
 * @Author: ming
 * @Date: 2022/4/6 17:33
 * 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知
 * 道每个数字重复了几次。请找出数组中任意一个重复的数字。
 */
public class Solution1 {

    public static int findRepeatNumber2(int[] nums) {
        boolean[] repeat = new boolean[100000];
        for (int num : nums) {
            if (repeat[num]) {
                return num;
            }
            repeat[num] = true;
        }
        return -1;
    }
}

4, 二维数组中的查找

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vttxzsxc-1649599445115)(%E5%89%91%E6%8C%87Offer.assets/1649261331841.png)]

解题思路

方法一:

这个题因为n不太大,所以可以使用双重for循环进行遍历查询。

方法二:

但是这道题既然被弄成中等难度的题,肯定不是让用双重for循环进行的。然后又被告知从左到右从上到下是递增的,所以要找比target小的值一定是从右往左或者从下往上,所以就开始一个个尝试。

发现如果从左上角开始的话,无论是行还是列都是递增的,无法确定行动轨迹。

如果从右上角开始的话,可以如果小于这个值就向下走,大于这个值就像左走。

如果从左下角开始的话,如果小于这个值就向右走,大于这个值就向上走。

如果从右下角开始的话,无论哪个方向都是减少的所以行不通。

代码实现

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution2
 * @Description: 二维数组中的查找
 * @Author: ming
 * @Date: 2022/4/6 21:25
 * 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
 */
public class Solution2 {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0) {
            return false;
        }
        //n行
        int n = matrix.length;
        //m列
        int m = matrix[0].length;
        //行
        int row = 0;
        //列
        int line = m - 1;
        while (row < n && line >= 0) {
            if (matrix[row][line] < target) {
                row++;
            }else if (matrix[row][line] > target) {
                line--;
            }else {
                return true;
            }
        }
        return false;
    }

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

        return false;
    }

    public boolean findNumberIn2DArray3(int[][] matrix, int target) {
        if (matrix == null || matrix.length == 0) {
            return false;
        }
        //n行
        int n = matrix.length;
        //m列
        int m = matrix[0].length;
        //行
        int row = n-1;
        //列
        int line = 0;
        while (row >= 0 && line < m - 1) {
            if (matrix[row][line] < target) {
                line++;
            }else if (matrix[row][line] > target) {
                row++;
            }else {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        int[][] a = {{1, 4,  7, 11, 15},
                {2,   5,  8, 12, 19},
  {3,   6,  9, 16, 22},
                {10, 13, 14, 17, 24}
};
        System.out.println(a[0].length);
    }
}

5,替换空格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LqF4cE6v-1649599445115)(%E5%89%91%E6%8C%87Offer.assets/1649332654152.png)]

思路分析:

  • 直接使用String的replace方法。
  • 可以创建一个新的StringBuilder来一个个往里存,如果是" ",就存%20,否则就存原本的字符

代码实现:

方法一

public static String replaceSpace2(String s) {
    return s.replace(" ", "%20");
}

方法二

public static String replaceSpace(String s) {
    StringBuilder stringBuilder = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == ' ') {
            stringBuilder.append("%20");
        }else {
            stringBuilder.append(s.charAt(i));
        }
    }
    return stringBuilder.toString();
}

6,从尾到头打印链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4HYuCwh4-1649599445115)(%E5%89%91%E6%8C%87Offer.assets/1649332887467.png)]

import java.util.*;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution4
 * @Description: 请描述该类的功能
 * @Author: ming
 * @Date: 2022/4/7 17:29
 */
public class Solution4 {


     public static class ListNode {
          int val;
          ListNode next;
          ListNode(int x) { val = x; }
     }

    //先创建一个数组,然后找node节点,从数组的尾部添加
    public int[] reversePrint(ListNode head) {
        int count = 0;
        ListNode node = head;
        while (node != null) {
            count++;
            node = node.next;
        }
        int[] nums = new int[count];
        node = head;
        while (node != null) {
            nums[--count] = node.val;
            node = node.next;
        }
        return nums;
    }

    //将head中的数据放入list中,然后调用Collections的反转方法进行反转,最后变回int【】
    public int[] reversePrint2(ListNode head) {
        ArrayList<Integer> integers = new ArrayList<>();
        int count = 0;
        while (head != null) {
            count++;
            integers.add(head.val);
            head = head.next;
        }
        Collections.reverse(integers);
        int[] arr1 = integers.stream().mapToInt(Integer::valueOf).toArray();

        return arr1;
    }

    //递归
    public static int[] reversePrint3(ListNode head) {
        ListNode node = head;
        int count = 0;
        while (node != null) {
            count++;
            node = node.next;
        }
        ArrayList<Integer> arrayList = new ArrayList();
        int[] nums = new int[count];
        dfs(head,arrayList);
        for (int i = 0; i < nums.length; i++) {
            nums[i] = arrayList.get(i);
        }
        return nums;
    }
    public static void dfs(ListNode head, ArrayList arrayList) {
        if (head == null) {
            return;
        }
        dfs(head.next, arrayList);
        arrayList.add(head.val);
    }

    public static void main(String[] args) {
        ListNode node = new ListNode(1);
        node.next = new ListNode(3);
        node.next.next = new ListNode(2);
        int[] ints = reversePrint3(node);
        for (int anInt : ints) {
            System.out.println(anInt);
        }
    }
}

7,重建二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vZPbjYDL-1649599445116)(%E5%89%91%E6%8C%87Offer.assets/1649339220986.png)]

思路分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2nLXdom-1649599445116)(%E5%89%91%E6%8C%87Offer.assets/1649339274961.png)]

对于以下数据:
preorder = [3,9,6,10,20,15,7] # 先序遍历
inorder = [6,9,10,3,15,20,7] # 中序遍历

1、根据[先序遍历],可以确定【3】为根节点,然后根据【3】在[中序遍历]进行划分:
- [6, 9, 10] 是【左子树】
- [3] 是此轮的【根节点】
- [15, 20, 7] 是【右子树】

2、对【左子树】进行同样的操作,递归传入的数组:
preorder = [9, 6, 10] # 先序遍历
inorder = [6, 9, 10] # 中序遍历

3、对【右子树】进行同样的操作,递归传入的数组:
preorder = [20, 15, 7] # 先序遍历
inorder = [15, 20, 7] # 中序遍历

方法步骤归纳:
1. 由【先序遍历第一个数字】得出根节点
2. 由【中序遍历结合根节点的值得出】:哪些数字是左子树,哪些数字是右子树,并划分出来
3. 重复 1-2

代码实现

import java.util.Arrays;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution5
 * @Description: 重建二叉树
 * @Author: ming
 * @Date: 2022/4/7 20:28
 * 输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
 *
 * 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
 *
 * 先序遍历的第一个节点一定是头节点,中序遍历根节点的左边一定都是左子树的节点,右边都是右子树的节点
 * 所以我们完全可以通过先序遍历确定头节点,然后去中序遍历中找到头节点的位置,然后通过将其分为左右两
 * 部分,然后通过递归进行之后的判断
 */
public class Solution5 {

    public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int x) { val = x; }

        @Override
        public String toString() {
            return "TreeNode{" +
                    "val=" + val +
                    ", left=" + left +
                    ", right=" + right +
                    '}';
        }
    }
    public static TreeNode buildTree(int[] preorder, int[] inorder) {
        //终止条件为如果我们没有头节点可以分配的时候就将结束。
        if (preorder.length == 0) return null;
        int n = preorder.length;
        //根节点
        int rootVal = preorder[0];
        //根节点在中序的下标
        int rootIndex = 0;
        for (int i = 0; i < inorder.length; i++) {
            if (inorder[i] == rootVal) {
                rootIndex = i;
                break;
            }
        }
        TreeNode root = new TreeNode(rootVal);
        //preorder替换的位置是要找到哪部分在该节点的左边,哪部分在该节点的右边,这样子就可以判断下一个节点是左边的头节点还是右边的头节点
        //如果存在的话,左边的头节点一定是在先序遍历中现头节点的下一位,截至的位置是左边有多少节点,就往右移动多少位。
        root.left = buildTree(Arrays.copyOfRange(preorder,1,1 + rootIndex),Arrays.copyOfRange(inorder, 0, rootIndex));
        root.right = buildTree(Arrays.copyOfRange(preorder,1 + rootIndex, n),Arrays.copyOfRange(inorder, rootIndex + 1, inorder.length));
        return root;
    }

    public static void main(String[] args) {
        int[] preorder = {3,9,20,15,7};
        int[] inorder = {9,3,15,20,7};
        TreeNode treeNode = buildTree(preorder, inorder);
        System.out.println(treeNode.toString());
    }
}

8,青蛙跳台阶问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IWOnr0No-1649599445116)(%E5%89%91%E6%8C%87Offer.assets/1649494532254.png)]

解题思路

我们可以尝试这分析规律,在这道题中,第0层和第1层是确定的,都为1。且因为这个青蛙要么一次跳一步,要么一次跳两部,所以之后的情况可以用到我们之前计算的内容。就是当我们跳一步时,我们只要看前一层有多少中跳法,当我们跳两步的时候,我们只要看前两层有多少种跳法。

这两种情况之和就是我们最终的答案,

代码实现

import java.util.HashMap;

/**
 * @version v1.0
 * @ProjectName: 数据结构
 * @ClassName: Solution6
 * @Description: 青蛙跳台阶问题
 * @Author: ming
 * @Date: 2022/4/9 16:09
 * 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
 *
 * 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
 */
public class Solution6 {
    HashMap<Integer, Integer> map = new HashMap<>();

    /**
     * 在这道题目中,我们发现一个规律,就是出来第0层和第一层,其他层的情况都可以看作当你走的剩下两层和一层的情况和,
     * 例如说你的目标是走到第二层,那么你的走法就是在第0层走的情况和第一层走的情况之和。因为你每次要么走一步,要么走两步。
     * 所以我们只要吧前两层的情况和相加即可。  下面的这个方法是用map存取数据的
     * @param n
     * @return
     */
    public int numWays(int n) {
        if(map.containsKey(n)) {
            return map.get(n);
        }

        if (n == 0 || n == 1) {
            map.put(0,1);
            map.put(1,1);
            return 1;
        }

        int num = numWays(n-1) + numWays(n-2);
        int res = num % 1000000007;
        map.put(n,res);
        return res;
    }

    /**
     * 这个方法使用数组去存取的,但是需要注意的是,在往数组里存取结果的时候就需要进行取模操作,不然可能会装不下
     * @param n
     * @return
     */
    public static int numWays2(int n) {
        if (n == 0 || n == 1) {
            return 1;
        }
        int[] res = new int[n + 1];
        res[0] = 1;
        res[1] = 1;
        for (int i = 2; i <= n; i++) {
            res[i] = (res[i-1] + res[i-2]) % 1000000007;
        }
        return  res[n];
    }
}

9,旋转数组的最小数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5pWsRGx-1649599445117)(%E5%89%91%E6%8C%87Offer.assets/1649494944275.png)]

方法一

思路分析:

这道题的题意是我们要进行查找最小的元素,而这个数组之前是一个有序数组,但是经过了一次旋转。

顺着这个思路,因为是查找最小值,而查找算法中我们最熟悉的是二分法。所以就可以尝试的看看二分法是否可以使用。经过我们的查看,因为他只用旋转了一次,无外乎就是要么在中间值及其左边的位置旋转或者在中间值右边进行旋转,如果这个值实在左边旋转的话,就是最小值交换到了右边的最后几个位置。这样子只要我们让中间值和最后边的值进行比较,如果最右边的值一定大于中间值,现在的话一定是小于中间值的。然后继续往右边找。如果在右边旋转,那么中值的右边一定是有序的,且最小值在中值及其左边。

代码如下

public class Solution7 {
    public int minArray2(int[] numbers) {
        int l = 0;
        int r = numbers.length - 1;
        int mid = 0;
        while(l < r) {
            mid = l + ((r - l) >> 1);
            if (numbers[r] > numbers[mid]) {
                r = mid;
            }else if (numbers[r] < numbers[mid]) {
                l = mid + 1;
            } else {
                //右边和中间值都一样了就证明一定是重复元素,让右指针左移一位。
                r--;
            }
        }
        return numbers[l];
    }
}

方法二

思路分析

既然是最小值,那么就简单粗暴,直接调用API进行数组排序找下标为0的位置。

代码如下

public class Solution7 {
    public int minArray(int[] numbers) {
        Arrays.sort(numbers);
        return numbers[0];
    }
}

方法三

思路分析

因为之前是一个有序数组,交换之后一定会破坏他的有序性。那么在前一个大于后一个的元素的位置一定就是交换的位置,我们只要返回后一个位置即可。

代码实现

public class Solution7 {
    public int minArray(int[] numbers) {
        //如果交换了,用下面的方式判断
        for(int i = 0;i < numbers.length;i++) {
            if(numbers[i] > numbers[i+1]) {
                return numbers[i+1];
            }
        }
        return numbers[0];
    }
}

10,矩阵中的路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UfLu2ssJ-1649599445117)(%E5%89%91%E6%8C%87Offer.assets/1649519092720.png)]

思路分析

这个就是一个典型的回溯+深度优先遍历的题目。

代码实现

class Solution {
    public boolean exist(char[][] board, String word) {
		if(board == null || )
    }
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值