剑指offer(1-18)

1、数组中重复的数字

描述

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

数据范围:0\le n \le 10000 \0≤n≤10000 

进阶:时间复杂度O(n)\O(n) ,空间复杂度O(n)\O(n) 

示例1

输入:[2,3,1,0,2,5,3]

返回值:2

说明:2或3都是对的

public class Solution {
    
    public int duplicate (int[] numbers) {
        // write code here
       int[] countarray=new int[numbers.length];
       for(int i=0;i<numbers.length;i++) {
           countarray[numbers[i]]++;
           if(countarray[numbers[i]]>1) {
               return numbers[i];
           }
 
       }
       return -1;
    }
}

2、二维数组中的查找

描述

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

[

[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]

]

给定 target = 7,返回 true。

给定 target = 3,返回 false。

数据范围:矩阵的长宽满足 0 \le n,m \le 5000≤n,m≤500 , 矩阵中的值满足 0 \le val \le 10^90≤val≤109
进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n+m)O(n+m)

示例1

输入:7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]

返回值:true

说明:存在7,返回true

import java.util.*;
public class Solution {
    public boolean Find(int target, int [][] array) {
        if(array.length == 0)return false;
        int r = array.length;
        int l = array[0].length;
        int left = 0,down = r - 1;
        while(left<l && down>=0){
            int tmp = array[down][left];
            if(tmp == target)return true;
            else if(tmp < target)left++;
            else down--;
        }
        return false;
    }
}

3、替换空格

描述

请实现一个函数,将一个字符串s中的每个空格替换成“%20”。

例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

数据范围:0 \le len(s) \le 1000 \0≤len(s)≤1000 。保证字符串中的字符为大写英文字母、小写英文字母和空格中的一种。

示例1

输入:"We Are Happy"

返回值:"We%20Are%20Happy"

public class Solution {
    public String replaceSpace (String s) {
        String rep = "";
        char[] ss = s.toCharArray();
        for(char tmp:ss ){
            if(tmp == ' ') rep += "%20";
            else rep += tmp;
        }
        return rep;
    }
}

4、从尾到头打印链表

描述

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。

如输入{1,2,3}的链表如下图:

返回一个数组为[3,2,1]               0 <= 链表长度 <= 10000

示例1

输入:{1,2,3}         返回值:[3,2,1]

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

5、重建二叉树

描述

给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。

例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。

提示:

1.vin.length == pre.length

2.pre 和 vin 均无重复元素

3.vin出现的元素均出现在 pre里

4.只需要返回根结点,系统会自动输出整颗树做答案对比

数据范围:n \le 2000n≤2000,节点的值 -10000 \le val \le 10000−10000≤val≤10000

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

示例1

输入:[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]

返回值:{1,2,3,4,#,5,6,#,7,#,#,8}

说明:返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示

 

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
        return help(pre,vin,0,pre.length-1,0,vin.length-1);
    }
/*
前序   [1],[2,4,7],[3,5,6,8]
中序   [4,7,2],[1],[5,3,8,6]
*/
    public TreeNode help(int[] pre,int[] in,int left,int right,int left1,int right1){
        if(left>=pre.length||left1>in.length||left>right||left1>right1)
        return null;
        int value = pre[left];
        TreeNode node = new TreeNode(value);
        int count = left1;
        //在中序遍历中找到位置并计算长度
        while(in[count]!=value)
        count++;
        count -= left1;

        node.left = help(pre,in,left+1,left+count,left1,left1+count-1);
        node.right = help(pre,in,left+count+1,right,left1+count+1,right1);
        return node;
    }
}

6、二叉树的下一个节点

描述

给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。下图为一棵有9个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示

示例:

输入:{8,6,10,5,7,9,11},8

返回:9

解析:这个组装传入的子树根节点,其实就是整颗树,中序遍历{5,6,7,8,9,10,11},根节点8的下一个节点就是9,应该返回{9,10,11},后台只打印子树的下一个节点,所以只会打印9,如下图,其实都有指向左右孩子的指针,还有指向父节点的指针,下图没有画出来

数据范围:节点数满足 1 \le n \le 50 \1≤n≤50  ,节点上的值满足 1 \le val \le 100 \1≤val≤100 

要求:空间复杂度 O(1) \O(1)  ,时间复杂度 O(n)\O(n) 

输入描述:

输入分为2段,第一段是整体的二叉树,第二段是给定二叉树节点的值,后台会将这2个参数组装为一个二叉树局部的子树传入到函数GetNext里面,用户得到的输入只有一个子树根节点

返回值描述:

返回传入的子树根节点的下一个节点,后台会打印输出这个节点

示例1

输入:{8,6,10,5,7,9,11},8             返回值:9

import java.util.ArrayList;
/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    ArrayList<TreeLinkNode> nodes = new ArrayList<>();
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        //获取根节点
        TreeLinkNode root = pNode;
        while(root.next != null) root = root.next;
        //中序遍历打造nodes
        InOrder(root);
        //进行匹配
        int n = nodes.size();
        for(int i = 0;i < n - 1;i++){
            TreeLinkNode cur = nodes.get(i);
            if(pNode == cur){
                return nodes.get(i+1);
            }
        }
        return null;
    }
    public void InOrder(TreeLinkNode root){
        if(root != null){
            InOrder(root.left);
            nodes.add(root);
            InOrder(root.right);
        }
    }
}

7、用两个栈实现队列

描述

用两个栈来实现一个队列,使用n个元素来完成 n 次在队列尾部插入整数(push)和n次在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

数据范围: n\le1000n≤1000

要求:存储n个元素的空间复杂度为 O(n)O(n) ,插入与删除的时间复杂度都是 O(1)O(1)

示例1

输入:["PSH1","PSH2","POP","POP"]

返回值:1,2

说明:"PSH1":代表将1插入队列尾部 "PSH2":代表将2插入队列尾部 "POP“:代表删除一个元素,先进先出=>返回1 "POP“:代表删除一个元素,先进先出=>返回2

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack2.size() <= 0){
            while(stack1.size() != 0){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    
    }
}

8、斐波那契数列

描述

大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。

斐波那契数列是一个满足 fib(x)=\left\{ \begin{array}{rcl} 1 & {x=1,2}\\ fib(x-1)+fib(x-2) &{x>2}\\ \end{array} \right.fib(x)={1fib(x−1)+fib(x−2)​x=1,2x>2​ 的数列

数据范围:1\leq n\leq 401≤n≤40

要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n) ,本题也有时间复杂度 O(logn)O(logn) 的解法

输入描述:一个正整数n

返回值描述:输出一个正整数。

示例1

输入:4     返回值:3

public class Solution {
    public int Fibonacci(int n) {
        if (n < 1 || n > 40) return 0;
        if (n <= 2)
            return 1;
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

9、旋转数组的最小数字

描述

有一个长度为 n 的非降序数组,比如[1,2,3,4,5],将它进行旋转,即把一个数组最开始的若干个元素搬到数组的末尾,变成一个旋转数组,比如变成了[3,4,5,1,2],或者[4,5,1,2,3]这样的。请问,给定这样一个旋转数组,求数组中的最小值。

数据范围:1 \le n \le 100001≤n≤10000,数组中任意元素的值: 0 \le val \le 100000≤val≤10000

要求:空间复杂度:O(1)O(1) ,时间复杂度:O(logn)O(logn)

示例1

输入:[3,4,5,1,2]    返回值:1

public class Solution {
    public int minNumberInRotateArray(int [] array) {
    //特殊情况判断
    if(array.length == 0) return 0;
    //左右指针i,j
    int i = 0,j = array.length - 1;
    //循环
    while(i<j){
        int m = (i+j)/2;
        if(array[m]>array[j])i = m + 1;
        else if(array[m]<array[j])j = m;
        else j--;
    }
    return array[i];
    }
}

10、矩阵中的路径

描述

请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 

[[a,b,c,e],[s,f,c,s],[a,d,e,e]]

矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

数据范围:0 ≤n,m≤20 ,1≤len≤25 

示例1

输入:[[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcced" 返回值:true

public class Solution {
  
    public boolean hasPath (char[][] matrix, String word) {
        char[] words = word.toCharArray();
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                //从[i,j]这个坐标开始查找
                if(dfs(matrix,words,i,j,0))
                return true;
            }
        }
        return false;
    }
    boolean dfs(char[][] matrix,char[] word,int i,int j,int index){
    //边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
    //如果这个字符不等于matrix[i][j],说明验证这个坐标路径是走不通的,直接返回false
        if(i >= matrix.length || i < 0 || j >= matrix[0].length || j < 0 || matrix[i][j] != word[index])
        return false;
        //如果word的每个字符都查找完了,直接返回true
        if(index == word.length - 1)
        return true;
        //把当前坐标的值保存下来,为了在最后复原
        char tmp = matrix[i][j];
        //修改当前坐标的值,防止重复查找
        matrix[i][j] = '#';
        //走递归,沿着当前坐标的上下左右4个方向查找
        boolean res = dfs(matrix,word,i-1,j,index+1)
        ||dfs(matrix,word,i+1,j,index+1)
        ||dfs(matrix,word,i,j-1,index+1)
        ||dfs(matrix,word,i,j+1,index+1);
        //递归之后再把当前的坐标复原
        matrix[i][j] = tmp;
        return res;
    }
}

11、机器人的运动范围

描述

地上有一个 rows 行和 cols 列的方格。坐标从 [0,0] 到 [rows-1,cols-1] 。一个机器人从坐标 [0,0] 的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 threshold 的格子。 例如,当 threshold 为 18 时,机器人能够进入方格   [35,37] ,因为 3+5+3+7 = 18。但是,它不能进入方格 [35,38] ,因为 3+5+3+8 = 19 。请问该机器人能够达到多少个格子?

数据范围: 0 \le threshold \le 15 \0≤threshold≤15  ,1 \le rows,cols \le 100 \1≤rows,cols≤100 

进阶:空间复杂度 O(nm) \O(nm)  ,时间复杂度 O(nm) \O(nm) 

示例1

输入:1,2,3返回值:3

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        if(rows<=0||cols<=0||threshold<0)
        return 0;
        boolean[][] isVisited = new boolean[rows][cols];
        int count = movingCountCore(threshold,rows,cols,0,0,isVisited);
        return count;

    }
    private int movingCountCore(int threshold,int rows,int cols,int row,int col,boolean[][] isVisited){
        if(row<0||col<0||row>=rows||col >=cols||isVisited[row][col]||cal(row)+cal(col) > threshold)
        return 0;
        isVisited[row][col]=true;
        return 1 + movingCountCore(threshold, rows, cols, row - 1, col, isVisited)
                + movingCountCore(threshold, rows, cols, row + 1, col, isVisited)
                + movingCountCore(threshold, rows, cols, row, col - 1, isVisited)
                + movingCountCore(threshold, rows, cols, row, col + 1, isVisited);
    }
    private int cal(int num) {
        int sum = 0;
        while (num > 0) {
            sum += num % 10;
            num /= 10;
        }
        return sum;
    }
}

12、剪绳子

描述

给你一根长度为 n 的绳子,请把绳子剪成整数长的 m 段( m 、 n 都是整数, n > 1 并且 m > 1 , m <= n ),每段绳子的长度记为 k[1],...,k[m] 。请问 k[1]*k[2]*...*k[m] 可能的最大乘积是多少?例如,当绳子的长度是 8 时,我们把它剪成长度分别为 2、3、3 的三段,此时得到的最大乘积是 18 。数据范围: 2≤n≤60
进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n)

输入描述:

输入一个数n,意义见题面。

返回值描述:输出答案。

示例1

输入:8   返回值:18

复制说明:8 = 2 +3 +3 , 2*3*3=18

public class Solution {
    public int cutRope(int target) {
        
        if(target<=2)return 1;
        if(target==3)return 2;

        int res = target/3;
        int mod = target%3;
        if(mod == 0){
            return pow(3,res);
        }else if(mod == 1){
            return pow(3,res-1)*4;
        }else{
            return pow(3,res)*2;
        }
    }
    int pow(int a ,int n){
        int sum = 1;
        for(int i=0;i<n;i++){
            sum = sum * a;
        }
        return sum;
    }
}

13、二进制中1的个数

描述

输入一个整数 n ,输出该数32位二进制表示中1的个数。其中负数用补码表示。

数据范围:- 2^{31} <= n <= 2^{31}-1−231<=n<=231−1

即范围为:-2147483648<= n <= 2147483647−2147483648<=n<=2147483647

示例1

输入:10   返回值:2   说明:十进制中10的32位二进制表示为0000 0000 0000 0000 0000 0000 0000 1010,其中有两个1。

public class Solution {
    public int NumberOf1(int n) {
        int res = 0;
        //遍历32位
        for(int i=0;i<32;i++){
            //按位比较
            if((n & (1 << i)) != 0)
            res++;
        }
        return res;
    }
}

14、数值的整数次方

描述

实现函数 double Power(double base, int exponent),求base的exponent次方。

注意:

1.保证base和exponent不同时为0。

2.不得使用库函数,同时不需要考虑大数问题

3.有特殊判题,不用考虑小数点后面0的位数。

数据范围: ∣base∣≤100  , ∣exponent∣≤100  ,保证最终结果一定满足∣val∣≤10 4次方 
进阶:空间复杂度 O(1)  ,时间复杂度 O(n) 

示例1

输入:2.00000,3   返回值:8.00000

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent<0){
            base = 1/base;
            exponent = -exponent;
        }
        double base1 = 1.0;
        for(int i=0;i<exponent;i++){
            base1 = base1*base;
        }
        return base1;
  }
}

15、打印从1到最大的n位数

描述

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

1. 用返回一个整数列表来代替打印
2. n 为正整数,0 < n <= 5

示例1

输入:1    返回值:[1,2,3,4,5,6,7,8,9]

public class Solution {
    
    public int[] printNumbers (int n) {
        int max = 1;
        for(int i=0;i<n;i++){
            max = max*10;
        }
        int[] arr=new int[max-1];
        for(int i=0;i<max;i++){
            if(i==max-1)break;
            arr[i]=i+1;
        }
        return arr;
    }
}

16、删除链表的节点

描述

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

1.此题对比原题有改动

2.题目保证链表中节点的值互不相同

3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

数据范围:0<=链表节点值<=10000    0<=链表长度<=10000

示例1

输入:{2,5,1,9},5    返回值:{2,1,9}

说明:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 2 -> 1 -> 9

public class Solution {
    
    public ListNode deleteNode (ListNode head, int val) {
        //加入一个头节点
        ListNode res = new ListNode(0);
        res.next = head;
        //前序节点
        ListNode pre = res;
        //当前节点
        ListNode cur = head;
        while(cur != null){
            if(cur.val == val){
                pre.next = cur.next;
                break;
            }
            pre = cur;
            cur = cur.next;
        }
        return res.next;
    }
}

17、正则表达式匹配

描述

请实现一个函数用来匹配包括'.'和'*'的正则表达式。

1.模式中的字符'.'表示任意一个字符

2.模式中的字符'*'表示它前面的字符可以出现任意次(包含0次)。

在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配

数据范围:

1.str 只包含从 a-z 的小写字母。

2.pattern 只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。

3. 0 \le str.length \le 26 \0≤str.length≤26 

4. 0 \le pattern.length \le 26 \0≤pattern.length≤26 

示例1

输入:"aaa","a*a" 返回值:true

说明:中间的*可以出现任意次的a,所以可以出现1次a,能匹配上

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param str string字符串
     * @param pattern string字符串
     * @return bool布尔型
     */
    public boolean match (String str, String pattern) {
        // write code here
        int n = str.length();
        int m = pattern.length();
        boolean[][] f = new boolean[n + 1][m + 1];

        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                //分成空正则和非空正则两种
                if (j == 0) {
                    f[i][j] = i == 0;
                } else {
                    //非空正则分为两种情况 * 和 非*
                    if (pattern.charAt(j - 1) != '*') {
                        if (i > 0 && (str.charAt(i - 1) == pattern.charAt(j - 1) ||
                                      pattern.charAt(j - 1) == '.')) {
                            f[i][j] = f[i - 1][j - 1];
                        }
                    } else {
                        //碰到 * 了,分为看和不看两种情况
                        //不看
                        if (j >= 2) {
                            f[i][j] |= f[i][j - 2];
                        }
                        //看
                        if (i >= 1 && j >= 2 && (str.charAt(i - 1) == pattern.charAt(j - 2) ||
                                                 pattern.charAt(j - 2) == '.')) {
                            f[i][j] |= f[i - 1][j];
                        }
                    }
                }
            }
        }
        return f[n][m];
    }
}

18、表示数值的字符串

描述

请实现一个函数用来判断字符串str是否表示数值(包括科学计数法的数字,小数和整数)。

科学计数法的数字(按顺序)可以分成以下几个部分:

1.若干空格

2.一个整数或者小数

3.(可选)一个 'e' 或 'E' ,后面跟着一个整数(可正可负)

4.若干空格

小数(按顺序)可以分成以下几个部分:

1.若干空格

2.(可选)一个符号字符('+' 或 '-')

3. 可能是以下描述格式之一:

3.1 至少一位数字,后面跟着一个点 '.'

3.2 至少一位数字,后面跟着一个点 '.' ,后面再跟着至少一位数字

3.3 一个点 '.' ,后面跟着至少一位数字

4.若干空格

整数(按顺序)可以分成以下几个部分:

1.若干空格
2.(可选)一个符号字符('+' 或 '-')

3. 至少一位数字

4.若干空格
 

例如,字符串["+100","5e2","-123","3.1416","-1E-16"]都表示数值。

但是["12e","1a3.14","1.2.3","+-5","12e+4.3"]都不是数值。

提示: 1.1 <= str.length <= 25

2.str 仅含英文字母(大写和小写),数字(0-9),加号 '+' ,减号 '-' ,空格 ' ' 或者点 '.' 。

3.如果怀疑用例是不是能表示为数值的,可以使用python的print(float(str))去查看

进阶:时间复杂度O(n) ,空间复杂度O(n) 

示例1

输入:"123.45e+6"   返回值:true

import java.util.*;
import java.util.regex.Pattern;

public class Solution {
    
    public boolean isNumeric (String str) {
        // write code here
        //        ^表示开头 $ 表示结尾  java中两个\\ 代表一个\
        //        * 零次或多次匹配前面的字符或子表达式
        //        ?零次或一次匹配前面的字符或子表达式
        //        + 一次或多次匹配前面的字符或子表达式
        //        [] 字符集。匹配包含的任一字符
        //        (:? )匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配
        String p = "^[+-]?(\\d*\\.\\d+|\\d+(\\.\\d*)?)(?:[eE][+-]?\\d+)?$";
        return Pattern.matches(p,str);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值