牛客网面试算法必刷TOP101刷题记录(二)

栈和队列

BM49 表达式求值

描述

请写一个整数计算器,支持加减乘三种运算和括号。

数据范围:0≤∣s∣≤100,保证计算结果始终在整型范围内

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

思路

用op记录上一步的运算符,num记录当前数字,c表示当前位字符。

首先判断当前字符c是否为数字,因为可能是多位数字,因此如果是数字的话,当前数字变为num*10+c-'0'。

其次判断当前字符是否是左括号。如果是左括号,处理方式为将括号内的当做一个整体进行递归,递归起点为此位+1,终点为与之匹配的括号位-1。用下标j寻找与之匹配的括号位方法为采用一个技术count=1,如果新字符是左括号则count+1,为右括号则count-1,count=0说明j在匹配的括号位了,那么递归(i+1,j)(左闭右开,j不在递归范围内),然后i=j。

最后判断字符是否为非数字或者当前是字符串最后一位,如果是的话:

op为加,num进栈。

op为减,-num进栈。

op为乘法,num*stack.pop()进栈。

最后将栈中所有累加得到结果。

代码

 public int solve (String s) {
        System.out.println(s);
        Stack stack = new Stack();
        int res = 0;
        char[] charArray = s.toCharArray();
        int num=0;
        char sign='+';
        for(int i=0;i0){
                    if(charArray[j]=='(') count++;
                    if(charArray[j]==')') count--;
                    if(count!=0)j++;
                 }
                num = solve(s.substring(i+1,j));
                i = j;
            }
            if(!Character.isDigit(c) || i==charArray.length-1)
            {
                if(sign=='+'){
                    stack.push(num);
                }else if(sign=='-'){
                    stack.push(-1*num);
                }else if(sign=='*'){
                    stack.push(num*stack.pop());
                }
                sign = c;
                num = 0;
            }
        }
        while(!stack.isEmpty()){
            res += stack.pop();
        }
        return res;
    }

 哈希

BM50 两数之和

描述

给出一个整型数组 numbers 和一个目标值 target,请在数组中找出两个加起来等于目标值的数的下标,返回的下标按升序排列。

(注:返回的数组下标从1开始算起,保证target一定可以由数组里面2个数字相加得到)

数据范围:2≤len(numbers)≤105,−−10≤numbersi​≤109,0≤target≤109

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

思路

遍历一遍将数组元素和下标存入hashmap,再遍历一遍取出来。

代码

public int[] twoSum (int[] numbers, int target) {
        HashMap map = new HashMap<>();
        int[] res = new int[2];
        for (int i = 0; i < numbers.length; i++) {
            map.put(numbers[i], i);
        }
        for (int i = 0; i < numbers.length; i++) {
            if (map.containsKey(target - numbers[i]) && (map.get(target - numbers[i])!=i)) {
                if (map.get(target - numbers[i]) < i) {
                    res[0] = map.get(target - numbers[i])+1;
                    res[1] = i+1;
                } else {
                    res[1] = map.get(target - numbers[i])+1;
                    res[0] = i+1;
                }
            }
        }
        return res;
    }

BM51 数组中出现次数超过一半的数字

描述

给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

数据范围:n≤50000,数组中元素的值 0≤val≤10000

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

保证数组输入非空,且保证有解

思路

用count表示当前数的个数,并记录结果,如果每次遇到的数与当前数一样则数量加一,如果不一样则数量减一,同时如果count=0则更换结果。因为所求的那个数出现次数大于其他所有数,所以最后留下的一定是所求的那个数。

如果不是要求空间复杂度O(1)的话可以用hashmap

代码

public int MoreThanHalfNum_Solution (int[] numbers) {
        int res = numbers[0];
        int count = 1;
        for(int i=1;i

BM52 数组中只出现一次的两个数字

描述

一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

数据范围:数组长度 2≤n≤1000,数组中每个数的大小0 要求:空间复杂度 O(1),时间复杂度 O(n)

提示:输出时按非降序排列。

思路1

遍历数组把数字和出现个数存入hashmap,遍历hashmap把出现一次的数字放入结果

时间复杂度O(n)空间复杂度 O(n)

代码1 

public int[] FindNumsAppearOnce (int[] nums) {
        int[] res = new int[2];
        int i=0;
        HashMap hashMap = new HashMap<>();
        for(int n:nums){
            if(hashMap.containsKey(n)){
                hashMap.put(n,hashMap.get(n)+1);
            }else{
                hashMap.put(n,1);
            }
        }
        for(int key:hashMap.keySet()){
            if(hashMap.get(key).equals(1)){
                res[i++] = key;
            }
        }
        int temp=0;
        if(res[0]>res[1]){
            temp = res[1];
            res[1] = res[0];
            res[0] = temp;
        }
        return res;
    }

思路2

两个数字取异或 可以得出不同的那一位 因此如果是一堆数中只有一个数字出现一次、其他数字出现两次,那么通过所有数字异或就可以得出这个数字。但是题目中是两个数字,那么这种情况下将所有数字异或就可以得出最终求的那两个数不同的所有位。

比如如果结果是3(0011),6(0110),那么所有异或得出的结果就是5(0101)。根据异或结果我们可以得到他们不同的一位0001,可以通过这一位将所有的数字分成两组,和0001取交集为1的一组,取交集为0的一组,这样就能将结果的两个数分开。

代码2

public int[] FindNumsAppearOnce (int[] nums) {
        int[] res = new int[2];
        int tmp=0;
        //得到这两个数不同的那一位
        for(int num:nums){
            tmp ^= num;
        }
        System.out.println(tmp);
        int mask = 1;
        while((mask&tmp)==0){
            mask <<= 1;
        }
        int a = 0;
        int b = 0;
        for(int num:nums){
            if((num&tmp)==0){
                a ^= num;
            }else{
                b ^= num;
            }
        }
        if(a>b){
            res[0] = b;
            res[1] = a;
        }else{
            res[0] = a;
            res[1] = b;
        }
        return res;
    }

BM53 缺失的第一个正整数

描述 

给定一个无重复元素的整数数组nums,请你找出其中没有出现的最小的正整数

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

数据范围:

−2^31≤nums[i]≤2^31−1

0≤len(nums)≤5∗105

思路

遍历一遍存hashmap里,然后判断有没有

代码

public int minNumberDisappeared (int[] nums) {
        HashMap hashMap =  new HashMap<>();
        for(int num:nums){
            hashMap.put(num,true);
        }
        for(int i=1;i

BM54 三数之和

描述

给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。

数据范围:0≤n≤1000,数组中各个元素值满足 ∣val∣≤100

空间复杂度:O(n2),时间复杂度 O(n2)

注意:

  1. 三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
  2. 解集中不能包含重复的三元组。
例如,给定的数组 S = {-10 0 10 20 -10 -40},解集为(-10, -10, 20),(-10, 0, 10) 

思路

暴力法的话时间复杂度是O(n^3)

优化:首先将数组排序(快排,时间复杂度O(logn))只从头到尾遍历一个元素num[i],目标变为找相加和为-num[i]的两个数,采用双指针,初始左指针在最左(i+1),初始右指针在最右(num.length-1),如果两个指针的数相加小于目标值则左指针向右,如果两个指针的数相加大于目标值则右指针向左,如果相等则将三个数加入到结果集中,并且左指针向右一次、右指针向左一次(因为还要继续查找)。为了避免解集中出现重复三元组,如果遍历中出现num[i]和num[i-1]相等则continue跳过一次,另外是双指针查找时如果找到了同时num[left+1]和num[left]相等要跳过、num[right-1]和num[right]相等也要跳过。

代码

public ArrayList> threeSum (int[] num) {
        ArrayList> res = new ArrayList>();
        if (num.length < 3) return res;
        Arrays.sort(num);
        for (int i = 0; i < num.length - 2; i++) {
            if (i != 0 && num[i] == num[i - 1]) continue;
            int low = i + 1;
            int high = num.length - 1;
            while (low < high) {
                if (num[low] + num[high] == -num[i]) {
                    ArrayList temp = new ArrayList();
                    temp.add(num[i]);
                    temp.add(num[low]);
                    temp.add(num[high]);
                    res.add(temp);
                    while (low + 1 < high && num[low] == num[low + 1]) low++;
                    while (high - 1 > low && num[high] == num[high - 1]) high--;
                    low++;
                    high--;
                } else if (num[low] + num[high] < -num[i]) {
                    low++;
                } else {
                    high--;
                }
            }
        }
        return res;
    }

回溯 

BM55 没有重复项数字的全排列 

描述

给出一组数字,返回该组数字的所有排列

例如:

[1,2,3]的所有排列如下
[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1].
(以数字在数组中的位置靠前为优先级,按字典序排列输出。)

数据范围:数字个数 0

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

思路

回溯法

牛客网面试算法必刷TOP101刷题记录(二)_第1张图片

 得到题中结果集的过程如上所示,在有新数字没加入到当前集合的情况下一直向下深度遍历,直到所有数字都在时向上退一级,退到原有的节点所有子节点都遍历过时再向上退一级。

使用一个递归函数完成这个递归回退的过程,递归函数需要传递的参数是整个数组、当前已经遍历到的所有节点,因此参数为原数组和一个list。

递归出口为list的长度与原数组长度相同说明已经到达解空间树的叶子结点,此时返回。

递归函数中需要做的事包括:对整个数组进行遍历,如果当前数字已经在list了跳出一次循环对下一个数进行判断,如果当前数字不在List里则将该数字加进去,使用新的list继续向下递归。递归结束说明向下的所有节点遍历完了,将list删掉末尾一个节点 回退到上一层。

代码

 ArrayList> res = new ArrayList>();
    public ArrayList> permute (int[] num) {
        ArrayList list = new ArrayList<>();
        backTrack(num,list);
        return res;
    }
    public void backTrack(int[] num,ArrayList list){
        if(list.size()==num.length){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i=0;i

BM56 有重复项数字的全排列

描述

给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。

数据范围:0

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

思路

牛客网面试算法必刷TOP101刷题记录(二)_第2张图片

 解空间树的结构和上一题一样,不同的是当第一个[1]的所有分支已经都遍历过以后,下一次再到1不继续向下递归了,也就是增加了剪枝条件。为了能使相同数字相邻,先将整个原数组排序。用一个boolean数组mark标识所有的数字是否被遍历过,每次将当前节点加入list时都将mark[i]置为true,每次递归结束返回上一级删除当前节点时再将mark[i-1]置为false。

这样每次此节点遍历过或者是遍历到的数字如果其值与上一个数字一样且上一个数组没被遍历过(mark[i-1]==false说明上一个相同数字的所有分支都被遍历过了),应该对当前进行剪枝,即跳过此数字。

代码

boolean[] mark;
    ArrayList> res = new ArrayList>();
    public ArrayList> permuteUnique (int[] num) {
        mark = new boolean[num.length];
        ArrayList list = new ArrayList();
        Arrays.sort(num);
        backtrack(num, list);
        return res;
    }
    public void backtrack(int[] num, ArrayList list) {
        if (list.size() == num.length) {
            res.add(new ArrayList(list));
            return;
        }
        for (int i = 0; i < num.length; i++) {
            if (mark[i] || i > 0 && num[i] == num[i - 1] && !mark[i - 1]) continue;
            if (!mark[i]) {
                list.add(num[i]);
                mark[i] = true;
            }
            backtrack(num, list);
            list.remove(list.size() - 1);
            mark[i] = false;
        }
    }

BM57 岛屿数量

描述

给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。

岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。

例如:

输入

[

[1,1,0,0,0],

[0,1,0,1,1],

[0,0,0,1,1],

[0,0,0,0,0],

[0,0,1,1,1]

]

对应的输出为3

(注:存储的01数据其实是字符'0','1')

思路

遍历整个矩阵,如果元素为1则累加数加一、进入递归函数。在递归函数中,将当前位置置为0,然后如果上面一个不越界且为1则递归遍历上面一个,下、右、左同理。

代码

 public int solve (char[][] grid) {
        int count = 0;
        int row = grid.length;
        int column = grid[0].length;
        if(grid.length==0) return 0;
        for(int i=0;i=0 && grid[i-1][j]=='1'){
            dfs(i-1,j,grid);
        }
        if(i+1<=row-1 && grid[i+1][j]=='1'){
            dfs(i+1,j,grid);
        }
        if(j-1 >=0 && grid[i][j-1]=='1'){
            dfs(i,j-1,grid);
        }
        if(j+1 <=column-1 && grid[i][j+1]=='1'){
            dfs(i,j+1,grid);
        }
    }

BM58 字符串的排列

描述

输入一个长度为 n 字符串,打印出该字符串中字符的所有排列,你可以以任意顺序返回这个字符串数组。

例如输入字符串ABC,则输出由字符A,B,C所能排列出来的所有字符串ABC,ACB,BAC,BCA,CBA和CAB。

牛客网面试算法必刷TOP101刷题记录(二)_第3张图片

思路 

和上面那道重复数的全部排列一样的,只是改成字符串版了

代码

boolean[] track;
    ArrayList res = new ArrayList();
    public ArrayList Permutation (String str) {
        String[] strArray = str.split("");
        Arrays.sort(strArray);
        track = new boolean[str.length()];
        String s = "";
        if (str.length() == 0) {
            res.add(s);
            return res;
        }
        backtrack(strArray, s);
        return res;
    }
    public void backtrack(String[] strArray, String s) {
        for (int i = 0; i < strArray.length; i++) {
            if (s.length() == strArray.length) {
                res.add(s);
                return;
            }
            if (track[i] || i != 0 && strArray[i - 1].equals(strArray[i]) &&
                    !track[i - 1]) {
                continue;
            }
            if (!track[i]) {
                s += strArray[i];
                track[i] = true;
            }
            backtrack(strArray, s);
            s = s.substring(0, s.length() - 1);
            track[i] = false;
        }
    }

BM59 N皇后问题

描述

N 皇后问题是指在 n * n 的棋盘上要摆 n 个皇后,
要求:任何两个皇后不同行,不同列也不在同一条斜线上,
求给一个整数 n ,返回 n 皇后的摆法数。

数据范围: 1≤n≤9

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

例如当输入4时,对应的返回值为2,

对应的两种四皇后摆位如下图所示:

牛客网面试算法必刷TOP101刷题记录(二)_第4张图片

 思路

思路为遍历棋盘,如果点符合放置皇后的条件,将因该点不能放置皇后的点记录下来,然后递归在这个皇后的下一行遍历每一列寻找合适位置放置余下的皇后。每次传递已经放置的皇后数(同时此数字加1也是下次遍历的起点,因为N个皇后放在N行N列肯定每一行都有一个皇后、每一列都有一个皇后,上一个皇后放在i行的话,下一个皇后一定出现在第i+1行)、棋盘大小。递归出口为当已放置的皇后数与要求一致时count++,return。

怎么记录不能放置皇后的点?当一个位置(i,j)放置了皇后以后,其所在行(x=i的所有点)、列(y=j的所有点)、对角线(x-y与j-i相等的所有点)、反对角线(x+y与i+j)上的点都不能再放置皇后了。因为每次传递了下一次遍历的起点行,为上一次遍历的下一行,行不会重复,因此不需要记录不能放置的行。列、对角线、反对角线分别用三个HashSet记录(查找比较快)。每次递归时候如果当前坐标不满足条件则跳过,满足条件则进行下一次递归。

代码

 int count = 0;
    //列
    HashSet column = new HashSet<>();
    //正斜线
    HashSet pos = new HashSet<>();
    //反斜线
    HashSet neg = new HashSet<>();
    public int Nqueen (int n) {
        queen(n, 0);
        return count;
    }
    public void queen(int n, int i) {
        if (i == n) {
            count++;
            return;
        }
        for (int j = 0; j < n; j++) {
            if (column.contains(j) || pos.contains(i-j) || neg.contains(i + j)) {
                continue;
            }
            column.add(j);
            pos.add(i-j);
            neg.add(i + j);
            queen(n, i + 1);
            column.remove(j);
            pos.remove(i-j);
            neg.remove(i + j);
        }
    }

BM60 括号生成

描述

给出n对括号,请编写一个函数来生成所有的由n对括号组成的合法组合。

例如,给出n=3,解集为:

"((()))", "(()())", "(())()", "()()()", "()(())"

数据范围:0≤n≤10

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

思路1(超时)

把每个括号当作不重复的字符串,得到所有排列组合,用函数判断是否合法,合法的放到HashSet去重。

代码1

 HashSet res = new HashSet();
    boolean[] mark;
    public ArrayList generateParenthesis (int n) {
        if (n == 0) {
            res.add("");
            return new ArrayList(res);
        }
        ArrayList strList = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            strList.add("(");
        }
        for (int i = 0; i < n; i++) {
            strList.add(")");
        }
        mark = new boolean[n * 2];
        String s = "";
        backtrack(s, strList);
        res.forEach(r-> System.out.println(r));
        return new ArrayList(res);
    }
    public void backtrack(String s, ArrayList strList) {
        if (s.length() == strList.size()) {
            if (isValid(s)) {
                res.add(s);
            }
            return;
        }
        for (int i = 0; i < strList.size(); i++) {
            if(mark[i]) continue;
            if (!mark[i]) {
                s += strList.get(i);
                mark[i] = true;
            }
            backtrack(s,strList);
            s = s.substring(0,s.length()-1);
            mark[i]=false;
        }
    }
    public boolean isValid(String str) {
        Stack stack = new Stack();
        if(str=="") return true;
        for(int i=0;i

思路2  

用hashmap存放现在可用的左括号数、右括号数。还有左括号的时候,用一个左括号,然后使用(+str作为中间结果继续递归,并将左括号可用数减一,递归完成后再将该数目加一还原。已经用的左括号数大于已经用的右括号数(即hashmap中左括号数小于右括号数)时候才可以用一个右括号,然后使用)+str作为中间结果继续递归。当左右括号都用光时递归结束,将中间结果放入结果集。

代码2

public ArrayList generateParenthesis (int n) {
        HashMap hashMap = new HashMap<>();
        ArrayList res = new ArrayList();
        hashMap.put("(",n);
        hashMap.put(")",n);
        recursion("",res,hashMap);
        return res;
    }
    public void recursion(String str,ArrayList res,HashMap hashMap){
        if(hashMap.get("(").equals(0) && hashMap.get(")").equals(0)) {
            res.add(str);
            return;
        }
        //剩余右括号大于剩余左括号才可以使用右括号
        if(hashMap.get(")").compareTo(0)>0 && hashMap.get(")").compareTo(hashMap.get("("))>0){
            hashMap.put(")",hashMap.get(")")-1);
            recursion(str+")",res,hashMap);
            hashMap.put(")",hashMap.get(")")+1);
        }
        //有左括号优先用左括号
        if(hashMap.get("(").compareTo(0)>0){
            hashMap.put("(",hashMap.get("(")-1);
            recursion(str+"(",res,hashMap);
            hashMap.put("(",hashMap.get("(")+1);
        }
    }

BM61 矩阵最长递增路径 

描述

给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。

这个路径必须满足以下条件:

1. 对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外。

2. 你不能走重复的单元格。即每个格子最多只能走一次。

数据范围:1≤n,m≤1000,0≤matrix[i][j]≤1000

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

例如:当输入为[[1,2,3],[4,5,6],[7,8,9]]时,对应的输出为5,

其中的一条最长递增路径如下图所示:

牛客网面试算法必刷TOP101刷题记录(二)_第5张图片

 思路

用一个布尔型的mark数组记录该点是否可以走,用一个list记录当前路径走过的点,用一个maxLength量记录当前最常路径长度。如果数组越界或者当前节点的权重小于路径上一个节点,说明不能继续走,此时比较路径长度是否可以替换最大长度、返回。如果当前list为空且当前节点可以走、或者list不为空、当前节点可以走、list的最后一个元素权重小于当前节点值则可以继续走,将当前节点加入List并继续递归其上、下、左、右路径。递归结束后将当前节点移除。

在主函数中遍历矩阵每一个元素,调用递归函数。

代码

int maxLength = 0;
    public int solve (int[][] matrix) {
        LinkedList list = new LinkedList<>();
        int rowLength = matrix.length;
        int columnLength = matrix[0].length;
        boolean[][] mark = new boolean[rowLength][columnLength];
        for (int i = 0; i < rowLength; i++)
            for (int j = 0; j < columnLength; j++)
                mark[i][j] = true;
        for (int i = 0; i < rowLength; i++)
            for (int j = 0; j < columnLength; j++)
                backtrack(matrix, list, i, j, mark);
        return maxLength;
    }
    public void backtrack(int[][] matrix, LinkedList list, int i, int j,
                          boolean[][] mark) {
        int rowLength = matrix.length;
        int columnLength = matrix[0].length;
        if (i  < 0 || j < 0 || i > rowLength - 1 ||
                j > columnLength-1) {
            if (list.size() > maxLength) maxLength = list.size();
            return;
        }
        if (!list.isEmpty() && (!mark[i][j] || matrix[i][j] < list.getLast())) {
            if (list.size() > maxLength) maxLength = list.size();
            return;
        }
        // System.out.println("i:" + i + "j:" + j);
        if (list.isEmpty() || mark[i][j] && matrix[i][j] > list.getLast()) {
            list.add(matrix[i][j]);
            mark[i][j] = false;
            backtrack(matrix, list, i - 1, j, mark);
            backtrack(matrix, list, i + 1, j, mark);
            backtrack(matrix, list, i, j - 1, mark);
            backtrack(matrix, list, i, j + 1, mark);
            mark[i][j] = true;
            list.removeLast();
        }
    }

动态规划 

BM62 斐波那契数列

描述

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

斐波那契数列是一个满足fib(x)=1,x=1,21 fib(x)=fib(x−1)+fib(x−2)​,x>2​ 的数列

数据范围:1≤n≤40

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

思路

递归

代码

  public int Fibonacci (int n) {
        if(n==1 || n==2) return 1;
        return Fibonacci(n-1)+Fibonacci(n-2);
    }

BM63 跳台阶

描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

数据范围:1≤n≤40

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

思路

台阶数为1时青蛙只有一种跳法,台阶数为2时青蛙有两种跳法。台阶数为n时青蛙跳的最后一步可以是只跳一个台阶,那么跳法数f(n-1),跳的最后一步也可以是跳两个台阶,那么跳法数为f(n-2),因为最后一步只有这两种跳法,所以f(n)=f(n-1)+f(n-2)

代码

public int jumpFloor (int number) {
       if(number==1) return 1;
       if(number==2) return 2;
       return jumpFloor(number-2)+jumpFloor(number-1);
    }

BM64 最小花费爬楼梯

描述

给定一个整数数组 cost  ,其中cost[i]  是从楼梯第i 个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

数据范围:数组长度满足 1≤n≤105  ,数组中的值满足 1≤costi​≤104 

思路

  • step 1:可以用一个数组记录每次爬到第i阶楼梯的最小花费,然后每增加一级台阶就转移一次状态,最终得到结果。
  • step 2:(初始状态) 因为可以直接从第0级或是第1级台阶开始,因此这两级的花费都直接为0.
  • step 3:(状态转移) 每次到一个台阶,只有两种情况,要么是它前一级台阶向上一步,要么是它前两级的台阶向上两步,因为在前面的台阶花费我们都得到了,因此每次更新最小值即可,转移方程为:dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

代码

 public int minCostClimbingStairs (int[] cost) {
       int[] dp = new int[cost.length+1];
       for(int i=2;i<=cost.length;i++){
        dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
       }
       return dp[cost.length];
    }

BM65 最长公共子序列(二)

描述

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列

数据范围:0≤∣str1∣,∣str2∣≤2000

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

思路

  • step 1:优先检查特殊情况。
  • step 2:获取最长公共子序列的长度可以使用动态规划,我们以dp[i][j]表示在s1中以i结尾,s2中以j结尾的字符串的最长公共子序列长度。
  • step 3:遍历两个字符串的所有位置,开始状态转移:若是i位与j位的字符相等,则该问题可以变成1+dp[i−1][j−1],即到此处为止最长公共子序列长度由前面的结果加1。
  • step 4:若是不相等,说明到此处为止的子串,最后一位不可能同时属于最长公共子序列,毕竟它们都不相同,因此我们考虑换成两个子问题,]dp[i][j−1]或者dp[i−1][j],我们取较大的一个就可以了。

创建一个二维数组num[str1.length() + 1][str2.length() +1]用来保存str1长度为i,str2长度为j时他们的最长公共子序列的长度

首先将第0行0列都赋0,接着按照上面的规则填写,可以得到如下矩阵:

牛客网面试算法必刷TOP101刷题记录(二)_第6张图片

题目中要求输出的时两个字符串的最长公共子序列而不是该子序列的长度,不过问题不大,我们重新复盘一下上述对求最长公共子序列长度的分析,不难发现,对于i >0,j>0时,num[i][j]的数据来源有两种情况:

1.如果str1.charAt(i - 1) == str2.charAt(j - 1),那么num[i][j]一定来源于num[i - 1][j - 1]+1;

2.如果str1.charAt(i - 1) != str2.charAt(j - 1),那么num[i][j]来源于num[i - 1][j]和num[i][j - 1]二者里面的较大值。
把上述三种数据来源的方位分别用1,2,3来标记(1来自左上方,2来自左边,3来自上面)

定义一个direction数组,为了方便起见,他的大小应该与num数组大小相同,direction[i][j]用来保存num[i][j]数据的来源方位

牛客网面试算法必刷TOP101刷题记录(二)_第7张图片

 从direction数组的右下角开始,遇到1就加入结果字符串的左边,并向左上,遇到2则向左、遇到3则向上,就可以得到结果了。

牛客网面试算法必刷TOP101刷题记录(二)_第8张图片

代码 

 public String LCS (String s1, String s2) {
        int[][] dp = new int[s1.length() + 1][s2.length() + 1];
        int[][] direction = new int[s1.length() + 1][s2.length() + 1];
        String res = "";
        for (int i = 0; i < dp.length; i++) {
            for (int j = 0; j < dp[i].length; j++) {
                if (i == 0 || j == 0) {
                    dp[i][j] = 0;
                    direction[i][j] = 0;
                } else {
                    if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1] + 1;
                        direction[i][j] = 1;
                    } else {
                        if (dp[i - 1][j] > dp[i][j - 1]) {
                            dp[i][j] = dp[i - 1][j];
                            direction[i][j] = 3;
                        } else {
                            dp[i][j] = dp[i][j - 1];
                            direction[i][j] = 2;
                        }
                    }
                }
            }
        }
        int i = dp.length - 1;
        int j = direction[0].length - 1;
        while(i>0 && j>0) {
                if (direction[i][j] == 1) {
                    res = s1.charAt(i - 1) + res;
                    i--;
                    j--;
                } else if (direction[i][j] == 2) {
                    j--;
                } else if (direction[i][j] == 3) {
                    i--;
                } else {
                    i--;
                    j--;
                }
                if (i <= 0) break;
            }
        if (res.equals("")) {
            return "-1";
        }
        return res;
    }

你可能感兴趣的:(算法,数据结构)