牛客网剑指offer做题笔记(更新中)

本文是自己在牛客网上做剑指offer的笔记,目前在按照难度刷题,因此暂时以题目难度排序分类,等自己做完后会转成以题目类型分类。

入门难度

JZ7 - 斐波那契数列

题目描述:
斐波那契数列
首先了解斐波那契数列

斐波那契数列,就是斐波那契提出的数列。

其形式就是F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)。也就是第3项开始,每一项等于前两项之和。

在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用。

本题目便是要求我们构建一个斐波那契数列。有两种解法:

1. 递归解法:

由于斐波那契数列的构造过程可以看出,其**第3项开始,每一项等于前两项之和。**因此很容易可以得出以下递归解法代码:

public class Solution {
    
    
    public int Fibonacci(int n) {
    
    
    	//当递归到n == 0 时,返回0
        if(n<=0) {
    
    
            return 0;
          }
        //当递归到n == 1 时,返回1  
        if(n==1) {
    
    
            return 1;
          }
          //开始递归
        return Fibonacci(n-2)+Fibonacci(n-1);
}
}

但是递归过程中所分支的递归树大,极其容易引起栈溢出。因此根据其递归公式,可以推出动态规划的解决方法:

public class Solution {
    
    
    public int Fibonacci(int n) {
    
    
    	//本题中最大的num指为39,设置为40长度的数组,比需求长度稍微大一些
        int num[] = new int[40];
        //初始化第一个以及第二个数
        num[0] = 0;
        num[1] = 1;
        int index = 2;
        //开始循环构建动态规划数组,思路为从第三项开始,每一项为前两项之和
        while(index < 40){
    
    
            num[index] = num[index - 1] + num[index - 2];
            index ++;
        }
        return num[n];
    }
}
}

这样子由一棵较大的递归树变成了一个循环,其时间复杂度大大降低。

简单难度

JZ5 - 用两个栈实现队列

两个栈实现队列
首先了解什么是栈:

栈(stack)又名堆栈,它是一种运算受限的线性表限定仅在表尾进行插入和删除操作的线性表这一端被称为栈顶,相对地,把另一端称为栈底向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈

划重点:栈的数据加入和取出都只能在栈顶中。是后进先出

什么是队列:

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。

对队列的操作跟我们生活中排队一样,新来的在队尾,准备离开的在队头。是后进后出

用两个栈来实现队列的思路就是:

将队列的所有元素放到一个栈中。有元素进入队列时,将栈的所有元素弹出到另一个栈,再将此栈栈顶位置弹出。

因为栈是后进先出,先进后出,所以将所有先进的元素全部弹出并放入另一个栈,让先进的栈在另一个栈中称为后进的栈,此时便可以将其先取出,也就是达到了后进后出的目的。

每次取出队列元素都需要进行此操作。

代码:

import java.util.Stack;

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

    public int pop() {
    
    
        while (!stack1.empty()){
    
    
            stack2.push(stack1.pop());
        }
        return stack2.pop();
    }
}

JZ6 - 旋转数组的最小数字

旋转数组的最小数字
本题就是简单的找原本排序后的数组将最小的若干个元素移到最后,再寻找其最小值,难度不大。

由于本人的愚昧,第一次用了最为硬核的解法:

import java.util.ArrayList;
import java.util.Arrays;
public class Solution {
    
    
    public int minNumberInRotateArray(int [] array) {
    
    
            Arrays.sort(array);
            return array[0];
    }
}

成功通过,但是这并不是我们要的结果。

在看了别人的解题后,发现了可以使用二分查找的类似思想进行解题。

思路:

采用二分查找的思想,不断缩小某个区间,直到出现开始节点大于结束节点的区间,此时开始节点就是最小值。详细:

  1. 将首尾作为开始节点、结束节点
  2. 判断开始节点是否小于结束节点,若是说明此时便是找到了递增的区间,此时开始节点位置就是最小值
  3. 取中间值 = (开始节点 + 结束节点) / 2
  4. 判断中间节点值跟两边节点大小,若右边小取中间节点到右边区域,若左边小,取中间节点到作变区域。
  5. 重复2

代码演示


public class Solution {
    
    
    public int minNumberInRotateArray(int [] array) {
    
    
        // 以右边为基准点,开始二分查找
        int start = 0, end = array.length - 1, mid;
        while(start < end) {
    
    
            // 已经找到了包含最小值的非递减区间
            if(array[start] < array[end]) {
    
    
                return array[start];
            }
            mid = (start + end) / 2;
            // 右边大则舍弃右边(非递减/混合区间)
            if(array[end] > array[mid]) {
    
    
                end = mid;
            // 右边小则舍弃左边(混合区间)
            } else if(array[end] < array[mid]) {
    
    
                start = mid + 1;
            // 相等则右边缩小
            } else {
    
    
                end--;
            }
        }
 
        // 存在为0处理
        return start == end ? array[start] : 0;
    }
}

JZ-8 跳台阶

跳台阶

本题是一道比较简单的题,但是不知道为什么放在了中等难度。

题意比较清晰了,看到题干可以看出,每一步都与上一步息息相关,第一时间想到的就是动态规划解法:

动态规划的话就要维护一个动态规划数组。对于每一步的关系可以看出:

令f(n)表示从任意位置向上跳n个台阶的跳法个数,当从第一个台阶向上跳时,可以先跳一个,也可以先跳两个:跳一个时,则后续跳法为f(n-1)个;跳两个时,后续跳法为f(n-2),故f(n)=f(n-1)+f(n-2)

恰好其每一步关系就是就是一个斐波那契数列,由此可以得出代码:

    public int JumpFloor(int target) {
    
    
        int num[] = new int[500];
        num[0] = 1;
        num[1] = 2;
        int index = 2;
        //构建数组
        while(index < num.length){
    
    
            num[index] = num[index -1] + num[index - 2];
            index ++;
        }
        return num[target-1];
    }
}

此外还可以利用递归的方法处理。

有兴趣的可以去查查看。

JZ-9 变态跳台阶

变态跳台阶
对于变态跳台阶不过是把跳的步数取消了只能跳1步和2步的界限。

也就是在每一步中将从第一步到第n-1步的可能性都加上即可:

public class Solution {
    
    
        public int JumpFloorII(int target) {
    
    
            int num[] = new int[500];
            num[0] = 1;
            num[1] = 2;
            int index = 2;
            //利用循环把每一步的可能性加上
            while(index < num.length){
    
    
                for(int i = index - 1;i >= 0;i --){
    
    
                    num[index] += num[i];
                }            
                num[index] += 1;
                index ++;
            }
            return num[target - 1];
        }
}

在观看其他人的题解时发现,其实有一下数学关系:

f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)

f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)

可以得出:

f(n) = 2*f(n-1)

因此可以对以上代码进行简化:

public class Solution {
    
    
        public int JumpFloorII(int target) {
    
    
            int num[] = new int[500];
            num[0] = 1;
            num[1] = 2;
            int index = 2;
            while(index < num.length){
    
    
            //直接带入公式即可
                num[index] = num[index - 1] * 2;
                index ++;
            }
            return num[target - 1];
        }
}

Guess you like

Origin blog.csdn.net/Nimrod__/article/details/113794732