剑指offer JZ1-5刷题笔记

A 参考资料

  • 书本《剑指offer(第二版)》

JZ01 二位数组中的查找

1.题目描述

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

[
  [1,2,8,9],  
  [2,4,9,12],  
  [4,7,10,13], 
  [6,8,11,15]
]
给定 target = 7,返回 true。
给定 target = 3,返回 false

示例1

输入
	7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值
	true
说明
	存在7,返回true

2.暴力求解

当然最容易想到的是暴力求解,就是一个个查找,如果找到就返回true,没找到就返回false,代码很简单,没什么可说的。

public boolean findNumberIn2DArray(int[][] matrix, int target) {
    if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
        return false;
    }
    int rows = matrix.length, columns = matrix[0].length;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < columns; j++) {
            if (matrix[i][j] == target) {
                return true;
            }
        }
    }
    return false;
}

3.线性查找

题中说了每行都是递增的,每列也是递增的。所以我们查找的时候可以利用这个特性,如果我们从左上角开始找,当目标值target大于当前值的时候,我们需要往更大的找,但这个时候无论往右找还是往下找都是比当前值大,所以我们无法确定该往哪个方向找。同理右下角也一样,所以我们只能从右上角或者左下角开始找。我们就用上面的数据当target等于23的时候从右上角开始找,来画个图看一下

img

从右上角开始找有个方便的地方就是他左边的都是比他小的,他下边的都是比他大的,如果target大于当前值我们就往下边找,如果target小于当前值我们就往左边找,来看下代码。

    public boolean Find(int target, int[][] array) {
        if (array == null || array.length == 0 || array[0].length == 0) {
            return false;
        }
        int rows = array.length, col = array[0].length;
        //从第0行col - 1列开始查找,也就是第1行最后一列的那个数字开始
        int row = 0;
        int column = col - 1;
        while (row < rows && column >= 0) {
            //num表示当前值
            int num = array[row][column];
            if (num == target) {
                //如果找到直接返回
                return true;
            } else if (num > target) {
                //到前面查找
                column--;
            } else {
                //到下面查找
                row++;
            }
        }
        return false;
    }

当然从左下角查找也是可以的,因为左下角右边的值是比他大的,上边的值是比他小的,也能区分,代码和上面差不多,来看下

    public boolean Find(int target, int[][] array) {
        if (array == null || array.length == 0 || array[0].length == 0) {
            return false;
        }
        int rows = array.length, col = array[0].length;
        int row = rows - 1;
        int column = 0;
        while (row >= 0 && column < col) {
            int num = array[row][column];
            if (num == target) {
                //如果找到直接返回
                return true;
            } else if (num > target) {
                //往上面找
                row--;
            } else {
                //往右边找
                column++;
            }
        }
        return false;
    }

JZ02 替换空格

1.题目描述

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

示例1

输入
"We Are Happy"
返回值
"We%20Are%20Happy"

2.String.replaceAll()

直接调用String的封装方法

public String replaceSpace (String s) {
    if (s == null || "".equals(s))
        return s;
   return s.replaceAll(" ", "%20");
}

3.Spring.split()

常规方法,拆分加替换

public String replaceSpace (String s) {
    StringBuilder sb = new StringBuilder();
    if (s == null || "".equals(s))
        return s;
    String[] strs = s.split("");
    for (String str : strs) {
        if (" ".equals(str))
            sb.append("%20");
        else 
        sb.append(str);
    }
    return sb.toString();
}

4.传统解法

虽然Java版的参数传入是String类,不像C语言直接就是字符数组,也不能直接在数组后面扩容。但是其实可以利用StringBuffer类来实现这样的操作。具体的思路的话书上已经讲解了,这里主要是贴出程序来介绍Java的实现方式。

pubilc class Solution{
    pubilc String replaceSpace(String s){
        if(s == null){ return null; }
        StringBuffer str = new StringBuffer(s);
        int length = str.length();
        int spaceNum = 0;
        for(int i = 0;i < length;i++){    if(str.charAt(i) == ' '){ spaceNum++; } }

        int oldStr = length - 1;
        length += 2 * spaceNum;
        int newStr = length - 1;
        str.setLength(length);
        while(spaceNum > 0 && newStr >= 0){
            char ch = str.charAt(oldStr--);
            if(ch == ' '){ 
                str.setCharAt(newStr--,'0');
                str.setCharAt(newStr--,'2');
                str.setCharAt(newStr--,'%');
                spaceNum--;
            }
            else{ str.setCharAt(newStr--,ch); }
        }

        return str.toString();
    }
}

JZ03 从尾到头打印链表

1.题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

示例1

输入
{67,0,24,58}
返回值
[58,24,0,67]

2.解题

一、非递归

1. 分析

listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 listNode 即可得到逆序链表

2. 代码

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode tmp = listNode;
        while(tmp!=null){
            list.add(0,tmp.val);
            tmp = tmp.next;
        }
        return list;
    }
}

3. 复杂度

时间复杂度:O(n^2)

空间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndugQ35t-1620795871448)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921537-2de5fdce-dc30-44be-ad76-d8119b027bde.svg)]

二、递归

1. 分析

既然非递归都实现了,那么我们也可以利用递归,借助系统的"栈"帮忙打印

2. 代码

import java.util.*;
public class Solution {
    ArrayList<Integer> list = new ArrayList();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }
}

3. 复杂度

时间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imH3FR4w-1620795871450)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921566-b2ef8ff7-8760-41ef-ad2c-76725b493c08.svg)]

空间复杂度:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h21ye6vT-1620795871452)(https://cdn.nlark.com/yuque/0/2021/svg/2196885/1618720921579-3b6e130a-420f-4a18-9d2e-e57d90f82d74.svg)]

1.Java–链表ListNode

2.ArrayList详解,看这篇就够了

3.Java List.add()方法

4.牛客编程题高级数据结构格式说明(待整理)


JZ04 重建二叉树

1.题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

示例1

输入
{67,0,24,58}
返回值
[58,24,0,67]

2.解题一

关键是:利用前序序列根节点在前找到根节点,用根节点去中序序列划分成两部分,左部分是左子树,右部分是右子树。再利用子树长度去前序序列把前序序列中的左右子树找出来,同时可以找出根节点。递归进行此步骤,如果子树长度为0,则不需要生成子问题。

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

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

/**
 * 关键是:利用前序序列根节点在前找到根节点,用根节点去中序序列划分成两部分,左部分是左子树,右部分是右子树。再利用子树长度去前序序列把前序序列中的左右子树找出来,同时可以找出根节点。递归进行此步骤,如果子树长度为0,则不需要生成子问题。
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
        TreeNode root = new TreeNode(pre[0]);
        build(root, pre, 0, pre.length, in, 0, in.length);
        return root;
    }

    /**
     * 递归和二分思想,将问题不断划分,直到问题容易解决。
     * 做法是:对于一个根节点,先去中序序列中找到根节点的值所在位置,利用这个位置分成2部分,左部分的中序序列长度即为前序序列中左部分的中序序列长度,右部分亦然。
     * 然后开始生成子问题,如果序列长度为0则不需要生成子问题。否则:利用前序序列第一个元素为根节点的性质生成根节点,然后构造子问题。
     * @param root 根节点
     * @param pre 前序序列 范围是[pleft,pright)
     * @param in 中序序列 范围是[ileft,iright)
     */
    public void build(TreeNode root, int[] pre, int pleft, int pright, int[] in, int ileft, int iright) {
        int i;
        for (i = ileft; i < iright; i++) {
            if (in[i] == root.val) {//从中序序列寻找根节点的位置
                break;
            }
        }
        int t = i - ileft;
        if (t > 0) {//子树长度为0时不必生成子问题
            root.left = new TreeNode(pre[pleft + 1]);
            build(root.left, pre, pleft + 1, pleft + 1 + t, in, ileft, i);
        }

        if (pright - pleft - 1 - t > 0) {
            root.right = new TreeNode(pre[pleft + 1 + t]);
            build(root.right, pre, pleft + 1 + t, pright, in, i + 1, iright);
        }
    }
}

目前报错数组越界,应该是索引没有完全找对,这个方法确实最好把图画出来

3.解题二

思路:

前序遍历:根→左→右中序遍历:左→根→右

  1. 由前序遍历序列pre={1,2,4,7,3,5,6,8}可知根结点是1;

  2. 则在中序遍历序列in={4,7,2,1,5,3,8,6}中找到1,便可知1所在位置的左侧是左子树,1所在位置的右侧是右子树;

  3. 递归调用:将左子树和右子树分别看成一颗树,将其前序遍历序列、中序遍历序列分别传入到该方法中,便可得到左子树的根结点、右子树的根结点。此时需要用第一步得到的根结点连接它们;

  4. 递归调用的终止条件:直到传入数组为空,说明已经没有节点,直接返回null。

import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
       //递归调用的终止条件
       if(pre.length == 0 || in.length == 0){
            return null;
       }
       //由前序遍历得到该二叉树的根结点
       TreeNode root = new TreeNode(pre[0]);
       //在中序遍历中找根结点位置,进行左右子树的划分
       for(int i = 0; i < in.length; i++){
           if(root.val == in[i]) {
            //将左子树看成一棵二叉树调用该方法,可以得到左子树根结点,即上面根结点的左子结点
            root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
            //将右子树看成一棵二叉树调用该方法,可以得到右子树根结点,即上面根结点的右子结点
            root.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length));
            //找到根结点位置便跳出循环
            break;
           }
       }
       //返回根结点
       return root;
    }
}

1.关于Java中的Arrays.copyOfRange()方法


JZ04 重建二叉树

1.题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

示例1

输入
{67,0,24,58}
返回值
[58,24,0,67]

2.解题一

3.解题二


JZ04 重建二叉树

1.题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

示例1

输入
{67,0,24,58}
返回值
[58,24,0,67]

2.解题一

3.解题二


JZ04 重建二叉树

1.题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

示例1

输入
{67,0,24,58}
返回值
[58,24,0,67]

2.解题一

3.解题二


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是我,Zack

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值