剑指offer(3-10)

目录

03,数组中重复数字

04,二维数组中查找

05,替换空格

06,倒序打印链表

07,重建二叉树

09,两个栈实现队列

10-1,斐波那契数列(动态规划)

10-2,青蛙跳台阶问题(递归记忆法)


03,数组中重复数字

思路一:

使用一个辅助的数组,数组默认初始化为0,然后遍历数字数组,每遇到一个数字,就把这个数字对应的空数组里面的数值修改为1,当再次遍历到这个数值时,说明重复,直接的返回即可。

class Solution {
        public int findRepeatNumber(int[] nums) {
            int [] tempArr = new int[nums.length];
            for (int i = 0; i < nums.length; i++) {
                if (tempArr[nums[i]] == 0) {
                    tempArr[nums[i]] = 1;
                } else {
                    return nums[i];
                }
            }
            return 0;
        }
    }
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

思路二:

由于只需要找出数组中任意一个重复的数字,因此遍历数组,遇到重复的数字即返回。为了判断一个数字是否重复遇到,使用集合(哈希表)存储已经遇到的数字,如果遇到的一个数字已经在集合中,则当前的数字是重复数字。

  1. 初始化集合为空集合,重复的数字 repeat = -1
  2. 遍历数组中的每个元素:
    1. 将该元素加入集合中,判断是否添加成功。
    2. 如果添加失败,说明该元素已经在集合中,因此该元素是重复元素,将该元素的值赋给 repeat,并结束遍历。
class Solution {
        public int findRepeatNumber(int[] nums) {
            Set set=new HashSet();
            int repeat=-1;//初始化重复的元素为-1
            for(int num:nums){
                if(!set.add(num)){
                    //如果添加成功,方法返回true,否则返回false
                    repeat=num;
                }
            }
            return repeat;
        }
    }
  • 时间复杂度:O(n)
    • 遍历数组一遍。使用哈希集合(HashSet),添加元素的时间复杂度为 O(1),故总的时间复杂度是 O(n)。
  • 空间复杂度:O(n)。

    • 不重复的每个元素都可能存入集合,因此占用 O(n)额外空间。

 

04,二维数组中查找

待补充

 

05,替换空格

思路一:先求出题目中字符串的长度,然后对字符串中逐个字符进行判断,如果遇到空字符,就在新的字符串末尾追加%20,直到遍历完整个字符串,然后把新的字符串赋值给s串即可。

class Solution {
    public String replaceSpace(String s) {
       int len=s.length();
       String str=s;
       s="";
       for(int i=0;i<len;i++){
           if(str.charAt(i)== ' '){
               s=s.concat("%20");
           }else{
               s=s.concat(str.charAt(i)+"");
           }
       }
        return s;
    }
}
  • 时间复杂度:O(n),对字符串数组进行一次遍历。

  • 空间复杂度:O(n),申请和字符串数组相同容量的空间。

思路二:

可以先把字符串对象转换为一个字符数组,然后对数组中的字符逐个遍历,使用StringBuilder来接受新的字符串对象,每次遇到空格时,就追加%20即可。

class Solution {
    public String replaceSpace(String s) {
        char array[]=s.toCharArray();//字符串转换字符数组方法
        //stringBuilder每一次都在原始字符串上追加,不会创建新的字符串
        StringBuilder stringBuilder=new StringBuilder();
        for (char ch:array){//增强的for循环
            if(ch == ' ')
            {
                stringBuilder.append("%20");
            }else {
                stringBuilder.append(ch);
            }
        }
        s=stringBuilder.toString();
        return s;
    }
}
  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

06,倒序打印链表

思路:借助于栈先进后出的特点,遍历链表,把链表元素全部压入栈中,然后在逐个的把栈中元素放到数组中返回。

class Solution {
    //此链表带头节点
    public int[] reversePrint(ListNode head) {
        //借助于栈的先进后出
        Stack stack=new Stack();
        while (head!= null){
            stack.push(head.val);
            head=head.next;
        }
        int arr[]=new int[stack.size()];
        int i=0;
        while (!stack.isEmpty()){
            arr[i++]=(int)stack.pop();
        }
        return arr;
    }
}
  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

拓展思路:可以使用数组,先遍历链表长度,然后开辟数组空间,从后往前赋值数组。

07,重建二叉树

待补充

09,两个栈实现队列

思路:

让第一个栈的栈底作为队列的尾部,第一个栈的栈顶作为队列的头部,每一次入队列元素直接入栈顶即可,当需要删除队列尾部的元素的时候,借助于第二个栈,先把第一个栈元素全部压入第二个栈,然后抛出第二个栈的栈顶元素,最后重新把第二个栈的所有元素压入第一个栈即可。

class CQueue {
    Stack stack01;
    Stack stack02;
    //栈元素进行初始化,把所有元素全部放在stack01中
    public CQueue() {
        stack01=new Stack<Integer>();
        stack02=new Stack<Integer>();
    }
    
    public void appendTail(int value) {
       stack01.push(value);
    }
    
    public int deleteHead() {
        int value=0;
        if(stack01.isEmpty())
            value=-1;
        else {
            while (!stack01.isEmpty()){
                stack02.push(stack01.pop());
            }
            value=(int)stack02.pop();//删除栈顶的元素
            while (!stack02.isEmpty()){
                stack01.push(stack02.pop());
            }
        }
        return value;
    }
}
  • 入队列元素:时间复杂度:O(1)
  • 出队列元素:时间复杂度:O(N)

10-1,斐波那契数列(动态规划)

思路:根据题目说明,如果使用递归调用的话,当n大于30时候,很可能会超时,因为做了很多的重复计算,所以在这里考虑使用动态规划法:

  • 因为f0=0,f1=1,所以f2=f0+f1=1,f3=f1+f2=2,依此递增循环推导下去,最终就能推导出fn的值
  • 因为结果需要对1000000007取模,所以为了防止溢出,每次求出fn>=1000000007时,则可将fn减去1000000007,相当于在此处取模,这跟最终得到结果再取模结果是一样的, 因为fn最终肯定等于求得的值加上m个1000000007的和。
  • 循环结束res即为最终取模后的结果。
class Solution {
    public int fib(int n) {
        int f_2=0,f_1=1;
        int res=0;
        for(int i=0;i<n;i++){
            res=f_2+f_1;
            if(res>1000000007)
                res-=1000000007;
            f_1=f_2;
            f_2=res;
        }
        return res;
    }
}
  • 时间复杂度:O(n);
  • 空间复杂度:O(1);

10-2,青蛙跳台阶问题(递归记忆法)

思路:

  • 设跳上 n级台阶有 f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1级或 2级台阶。
    • 当为 1级台阶: 剩n−1个台阶,此情况共有 f(n−1)种跳法;
    • 当为 2级台阶: 剩 n−2个台阶,此情况共有 f(n−2)种跳法。
  • f(n)为以上两种情况之和,即 f(n)=f(n−1)+f(n−2),以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第n项的值
    • 青蛙跳台阶问题: f(0)=1,f(0)=1,f(0)=1
    • 斐波那契数列问题: f(0)=0, f(1)=1, f(2)=1
class Solution {
    public int numWays(int n) {
//本题目实际就是菲波那切数列的变体,使用数组方法
        if(n == 0 || n == 1)
            return 1;
        else {
            int[] arr = new int[n + 1];
            arr[0] = 1;
            arr[1] = 1;
            for (int i = 2; i < n + 1; i++) {
                arr[i] = (arr[i - 1] + arr[i - 2])%1000000007;
            }
            return arr[n];
        }
    }
  • 时间复杂度:O(n);
  • 空间复杂度:O(n);

小结:对于类似菲波那切数列这种问题的解法

  • 使用递归的方法,把求解f(n)的问题转化为求解f(n-1)和f(n-2)两个子问题,递归进行计算,使用f(0)和f(1)作为出口条件。
    • 缺点:递归进行大量的重复计算,浪费时间。
  • 记忆递归法:这种方法是在递归方法的基础上增加一个辅助数组,每一次把计算的结果保存在数组中,下次需要在计算某一个值的时候直接拿出来使用,不需要重复计算。
    • 缺点:保存计算出的数值需要O(n)的空间。
  • 动态规划法:只记录最近需要的数值,不需要开辟空间,时间复杂度是O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值