剑指Offer(10-19)

继续刷剑指,提高效率,第二波。。

亮剑经典语录 二:
逢敌必亮剑,倒在对手的剑下不丢脸,丢脸的是不敢亮剑。

10. 斐波那契数列

题目描述:
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
解题思路:斐波那契数列,经典递归说明例子,不过递归的话从上到下递归,而且有很多重复项,这里应当使用动态规划来解决,时间复杂度O(n),空间复杂度O(1)。

public class Solution {
    public int Fibonacci(int n) {
        if (n <= 1) return n;
        int pre1 = 1;
        int pre2 = 0;
        for (int i = 2; i <= n; i++) {
            int cur = pre1 + pre2;
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
        
    }
}
11.矩阵覆盖

题目描述
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路:
其实代码跟第十题一样,只是换个思路,关键理解涂掉最后一级矩阵的时候,可以选择使用横向完成,也可以使用竖向完成,而使用横向完成是f(n - 1),使用竖向完成是f(n - 2)

public class Solution {
    public int RectCover(int target) {
        if (target <= 2) return target;
        int pre1 = 2, pre2 = 1;
        for (int i = 3; i <= target; i++) {
            int cur = pre1 + pre2;
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
    }
}
12.跳台阶

题目描述:
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
解题思路:
其实还是跟斐波那契数列一样,就是当target = 1 时,只有一种,target = 2时有两种,当target = n时 :F(n) = F(n - 1) + F(n - 2)

public class Solution {
    public int JumpFloor(int target) {
        if (target < 2) return target;
        int pre1 = 2, pre2 = 1;
        for (int i = 2; i < target; i++) {
            int cur = pre1 + pre2;
            pre2 = pre1;
            pre1 = cur;
        }
        return pre1;
    }
}
13.变态跳台阶

题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路:
思路跟12题差不多,只不过这题在求的过程中把值都暂时放在数组里,最后求的时候遍历数据这些求好的对应的阶级种数之和即为新的下级阶梯种数。需要注意每一个dp[i]都要++,因为都可以直接从第0阶一步跳过去。

public class Solution {
    public int JumpFloorII(int target) {
        if (target < 2) return target;
        int dp[] = new int[target];
        dp[0] = 1; dp[1] = 2;
        for (int i = 2;i< target; i++) {
            for (int j = 0; j < i; j++) {
                dp[i] += dp[j];
            }
            dp[i]++;
        }
        return dp[target - 1];
    }
}
14.旋转数组的最小数字

题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解题思路:
遍历一遍,因为两边是非递减的,所以如果下一个数字比上一个小,那么这个就是最小的。如果没找的,那么第一个就是最小的。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) return 0;
        for (int i = 0; i < array.length; i++) {
            if (array[i] > array[i+1]) {
                return array[i+1];
            }
        }
        return array[0];
    }
}

还可以皮一点:直接Array.sort();

import java.util.Arrays;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if (array.length == 0) return 0;
        Arrays.sort(array);
        return array[0];
    }
}
15.矩阵中的路径

题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bccced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

解题思路:
使用深度优先遍历,暴力搜索全部每一个字符的所有可能,这里根据题意,要进行回溯,在一次搜索结束时需要进行回溯,将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。
首先,题目给我们的是一维数组,我们需要将他转变为矩阵,也就是二维数组,然后再用深度搜索dfs解决,

public class Solution {
    private static int[][] direction = {{0,1},{0,-1},{1,0},{-1,0}};
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str){
        char m [][] = new char[rows][cols];
        //将一维矩阵转成二维矩阵
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                m[i][j] = matrix[i*cols+j];
            }
        }
        boolean[][] visited = new boolean[rows][cols];	//记录每个点是否被访问
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (str[0] == m[i][j] && dfs(m,i,j,str,visited,0)){
                    return true;
                }
            }
        }
        return false;
    }
    // index表示str的下标
    private boolean dfs(char[][] m, int i, int j, char[] str,boolean[][]visited,int index) {
        if (i >= m.length || i < 0 || j >= m[0].length || j < 0) return false;
        if (!visited[i][j] && m[i][j] == str[index]){
            if (index == str.length - 1) return true;
            else {
            visited[i][j] = true;
            //上下左右dfs
            for (int[] d : direction) {
                if (dfs(m,i + d[0],j + d[1],str,visited,index + 1)) return true;
            }
            visited[i][j] = false;    //回溯   
        }
     }
        return false;
      
    }
}
16. 表示数值中的字符串

题目描述:
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

解题思路:
如果按照常规逻辑判断的话 代码比较长,这里可以参考正则表达式进行求解。
具体可以参考菜鸟教程

public class Solution {
    public boolean isNumeric(char[] str) {
        if (str.length == 0 || str == null) return false;
        return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
    }
}
17. 调整数组顺序使奇数位于偶数前面

题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

解题思路:考虑拷贝一个相同的数组arr,首先计算出奇数的个数, 然后遍历判断。

public class Solution {
    public void reOrderArray(int [] array) {
        int count = 0;
        for (int i : array) {
            if (i % 2 != 0) {
                count++;	//奇数个数
            }
        }
        int first = 0;
        int len = array.length;
        int arr[] = array.clone();
        for (int i = 0; i < len; i++) {
            if (arr[i] % 2 != 0) {
                array[first++] = arr[i];
            } else {
                array[count++] = arr[i];
            }
        }
    }
}

也可以使用排序来实现,可以实现以时间换取空间,双重循环,时间复杂度O(n2)

18. 删除链表中重复的结点

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路:
递归版本,然后判断就是:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        if (pHead == null || pHead.next == null) return pHead;
        ListNode next = pHead.next;
        if (next.val == pHead.val) {
             while (next != null && pHead.val == next.val){
                 next = next.next;
             }
              return deleteDuplication(next);
            }else {
                pHead.next = deleteDuplication(next);
                return pHead;
          }       
    }
}

非递归版本:
有时候做链表相关的题目时,可以考虑new一个ListNode指向第一个指针,这样可以解决边界问题。
首先添加一个头节点head,以防止碰到第一个,第二个节点就相同的情况。设置 first ,last 指针, first指针指向当前确定不重复的那个节点,而last指针相当于工作指针,一直往后面搜索。

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead){
        if (pHead == null || pHead.next == null) return pHead;
        ListNode head = new ListNode(0);
        head.next = pHead;
        ListNode first = head;
        ListNode last = head.next;
        while (last != null) {
            if (last.next != null && last.next.val == last.val){
            	// 找到最后的一个相同节点
                while (last.next != null && last.next.val == last.val){
                    last = last.next;
                }
                first.next = last.next;
                last = last.next;
            } else {
                first = first.next;
                last = last.next;
            }
        }
        return head.next;        
        
    }
}
19.机器人的运动范围

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

解题思路:
采用DFS遍历搜索,定义一个是否访问的数组,如果已访问,就设为true,然后四个方向分别递归,这里要注意将坐标的值转换为值,比如(35,37)转换为(8,10)。类似的题目在LeetCode中还有岛屿问题,朋友圈问题,可以参考下。传送门

public class Solution {
    private int [][] direction = {{0,1},{0,-1},{1,0},{-1,0}};
    private int count = 0;
    private int threshold;
    private int rows;
    private int cols;
    private int[][] nums;
    public int movingCount(int threshold, int rows, int cols){
        this.threshold = threshold;
        this.rows = rows;
        this.cols = cols;
        boolean b[][] = new boolean[rows][cols];
        getNum();
        hasRange(b,0,0);
        return count;
        
        
    }
    //dfs遍历
    private void hasRange(boolean [][] b,int i, int j) {
        if (i >= rows || i < 0 || j >= cols || j < 0 || b[i][j] || this.nums[i][j] > threshold ){
            return;
        }
        b[i][j] = true;
        count++;
        for (int d[] : direction) {
            hasRange(b,i + d[0],j + d[1]);
        }
    }
    //将坐标转换为值
    private void getNum(){
        int [] nums2 = new int [Math.max(rows,cols)];
        for (int i = 0; i < nums2.length; i++) {
            int temp = i;
            while (temp > 0) {
                nums2[i] += temp % 10;
                temp /= 10;
            }
        }
        this.nums = new int[rows][cols];
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                this.nums[i][j] = nums2[i] + nums2[j];
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值