剑指offer刷题记录(七)

1.

我想到动态规划去了,但有点问题。看到大佬的递归解法很好,值得学习。

class Solution {
    public int countDigitOne(int n) {
        return f(n);
    }
    private int f(int n ) {
        if(n <= 0) return 0;
        String s = String.valueOf(n);//转成字符串
        int high = s.charAt(0) - '0';//返回指定索引的字符,参数就是索引值
        int pow = (int)Math.pow(10,s.length()-1);//Math聚合函数返回的是double类型的值,所以必须强制类型转换
        int last = n-high*pow;
        if(high == 1){
            return f(pow-1)+f(last)+1+last;
        }
        return high*f(pow-1)+pow+f(last);
    }
}

2.

这道题有一定难度,属于动态规划题,大佬使用的是动态规划+双指针

class Solution {
    public int lengthOfLongestSubstring(String s) {
        HashMap<Character,Integer> map = new HashMap<>();//记录每个字符上一次出现的索引值
        int tmp = 0;
        int res = 0;
        for(int j=0;j<s.length();j++){
            //i取值:如果s字符串第j位已经在map中有,重复了,i的值就是上次出现的索引值,如果没有出现过,就给i复制-1,
            int i = map.containsKey(s.charAt(j)) ? map.get(s.charAt(j)) : -1;
            map.put(s.charAt(j),j);
            if(tmp < j-i){
                tmp += 1;
            }else{
                tmp = j-i;
            }
            res = Math.max(res,tmp);
        }
        return res;
    }
}

3.

大佬使用深度优先搜索解这道题,dfs基本都是使用递归来实现,思路如下:

这里面要弄明白为什么它要把board[i][j]的值赋值‘/’,然后又将它复原。

class Solution {
    public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();//将word转化为字符数组
        for(int i=0;i<board.length;i++){
            for(int j=0;j<board[0].length;j++){
                //其实遍历就是在board数组里找到word第一个字符
                if(dfs(board,words,i,j,0)) return true;    
            }
        }
        //没有找到第一个字符
        return false;
    }
    public boolean dfs(char[][] board,char[] words,int i,int j,int k){
        if(i<0 || i>board.length-1 || j<0 || j>board[0].length-1 || board[i][j] != words[k]) return false;//判定就是如果行列超出界限或者board当前值不和word当前值相等,代表路走不下了
        if(k == words.length-1) return true;//代表走完了
        char tmp = board[i][j];
        board[i][j] = '/';//用这个来标记,防止重复走过
        boolean res = dfs(board,words,i+1,j,k+1) || dfs(board,words,i-1,j,k+1)|| dfs(board,words,i,j+1,k+1) ||dfs(board,words,i,j-1,k+1);
        board[i][j] = tmp;//复原
        return res;
    }
}

4.

这道题我也发现其中规律,如下:

但是在编程过程中,没有大佬的巧妙,比如这里start end digit count 这些关系等等。

大佬后面的求解剩下的数所对应的的哪个数字和这个数字的哪一位,设计的思路非常巧妙

 

最后实现的代码如下:

class Solution {
    public int findNthDigit(int n) {
        if(n<=9) return n;
        int dight = 1;
        long start = 1;
        long count = 9;
        int res = 0;
        while(n > count){
            n -= count;
            dight += 1;
            start *= 10;
            count = 9*start*dight;
        }
        long num = start+(n-1)/dight;//这个数的值
        int wei = (n-1) % dight;
        res = Long.toString(num).charAt(wei) - '0';
        return res;
    }
}

5.

思路理解:快速幂法

代码实现如下:

class Solution {
    public double myPow(double x, int n) {
        if(x == 0 || x == 1) return x;
        if(n == 0) return 1;
        long b = n;
        double res = 1.0;
        if(b < 0){
            x = 1/x;
            b = -b;
        }
        while(b > 0){
            if(b % 2 == 1){//奇数
                res *= x;
            }
            x *= x;
            b /= 2;
        } 
        return res;
    }
}

6.

思路相比于前面的剪绳子1,这里就是大数字,在就3的a次方结果时,超出了int的范围,所以得结合上题那种快速幂的方法。

class Solution {
    public int cuttingRope(int n) {
        if(n <= 3) return n-1;
        int b = n % 3;
        int p = 1000000007;
        //根据二分法计算原理,至少要保证变量 x 和 rem 可以正确存储 1000000007^21000000007 因此我们选取 long 类型。
        long res = 1;
        long x = 3;
        for(int a = n / 3 - 1;a > 0;a /= 2){//这里a的值退掉1的原因在于配合后面的余数b,如果余数b为0,那没事就是结果继续*3,如果余数b为1,可以将退掉的3和1变成2和2,余数b为2,那就3*2
            if(a % 2 == 1) res = (res * x) % p; 
            x = (x * x) % p;
        }
        if(b == 0) return (int)(res * 3 % p);
        if(b == 1) return (int)(res * 4 % p);
        return (int)(res * 6 % p);
    }
}

7.

class Solution {
    public int strToInt(String str) {
        //假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
        str = str.trim();//去掉前后空格
        char[] c = str.toCharArray();
        long rem = 0;
        int i = 1;//判断除了符号位,正式的位数从何开始
        if(c.length == 0) return 0;
        int sign = 1;//判断符号位,有三种情况,正负和无符号
        if(c[0] == '-') {
            sign = -1;       
        }else if(c[0] != '+'){
            i = 0;//无符号,从第一位还是搜索
        }
        for(int j=i;j<c.length;j++){
            //特殊情况,这一位不是数字
            if(c[j]<'0' || c[j] > '9') break;//这里break的原因是这里规定好像如果出现不是有效的整数就终止了
            rem = rem * 10 + (c[j] - '0');//将数组组合起来
            if(rem > Integer.MAX_VALUE){
                if(sign == -1){
                    return Integer.MIN_VALUE;
                }else{
                    return Integer.MAX_VALUE;
                }     
            }
        }     
        return sign * (int)rem;
    }
}

8.

我首先思路是按照以下几点来归纳是否是数值:

1.只能是数字,不能出现其他字符(除了一些特殊的)

2.小数点出现前,不能有小数点出现过

3.e出现前,不能有e出现过,且e后面不能没有数字了

4.符号+-只能出现在首位或者e的后第一位上

所以这里大佬的思路上,他用几个boolean值的变量,来充当标识符,来判断比如前面出没出现过小数点或者e。

class Solution {
    public boolean isNumber(String s) {
        if(s == null || s.length() == 0) return false;
        boolean num = false;//判断有无出现过数字
        boolean eflag = false;//判断有无出现过e
        boolean dot = false;//判断有无出现过小数点
        char[] c = s.trim().toCharArray();
        for(int i=0;i<c.length;i++){
            if(c[i] >= '0' && c[i] <= '9'){//除了一些特殊字符,必须为数字
                num = true;
            }else if(c[i] == '.' ){
                if(eflag || dot) return false;
                //num = false;//"3." 这里为true,说明允许小数点后面没有数字
                dot = true;
            }else if(c[i] == '+' || c[i] == '-'){
                if(i != 0 && c[i-1] != 'e'&& c[i-1] != 'E') return false;
            }else if(c[i] == 'e' || c[i] == 'E'){
                if(eflag || !num) return false;
                eflag = true;
                num = false;
            }
            else{
                return false;
            }
        }
        return num;
    }
}

9.

我的初步思路是:使用一个arraylist,不停往其中添加,然后使用sort方法进行排序,排序后按照list的size大小判断此时是偶数还是奇数,再做相应地安排。

代码如下:这样的思路简单,但是时间复杂度太高

class MedianFinder {
    ArrayList<Integer> list;

    /** initialize your data structure here. */
    public MedianFinder() {
        list = new ArrayList<>(); 
    }
    
    public void addNum(int num) {
        list.add(num);
    }
    
    public double findMedian() {
        Collections.sort(list);
        int size = list.size();
        double median = 0;
        if(size % 2 == 1){
            int index = size / 2;
            //index += 1;
            median = list.get(index);
        }else{
            int index = size / 2 ;
            median = (list.get(index-1) * 1.0+list.get(index)) / 2;
        }
        return median;
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

大佬设计了一个思路:使用两个堆,一个最小堆用于存放这些数列里大的一半数字,一个最大堆用于存放这些数列里小的一半数字。

就是排序工作基本就在add方法里完成了。最小堆(大小为m),最大堆(大小为n),当m=n时,添加元素先往B里添加,然后将其弹出,再插入A。当m不等于n时,先往A里添加再弹出,插入B。所以A里的元素个数永远是大于或者等于B的。所以在查找中位数时,如果m=n时,那就返回A,B堆顶元素的和的一半,如果m不等于n时,即m比n大1,返回A堆顶的元素。

这里堆的实现,用优先队列,默认就是最小堆,想使用最大堆,使用这样的语句 new PriorityQueue<Integer>((x,y)->(y-x))

class MedianFinder {
    Queue<Integer> A,B;
    /** initialize your data structure here. */
    public MedianFinder() {
        A = new PriorityQueue<Integer>(); //优先队列可以直接实现最小堆
        B = new PriorityQueue<Integer>((x,y)->(y-x)); //这样子的lamada表达式可以直接实现最大堆
    }
    public void addNum(int num) {
        int m = A.size();
        int n = B.size();
        if(m == n){//两个相等,就是往A里添加元素。先往B里添加再弹出B的栈顶元素给A。
        //思考一下为什么要这么添加?
            B.add(num);
            A.add(B.poll());
        }else{
            A.add(num);
            B.add(A.poll());
        }
    }
    
    public double findMedian() {
        int m = A.size();
        int n = B.size();
        if(m == n){
            return (A.peek()+B.peek()) / 2.0;
        }else{
            return A.peek();
        }
    }
}

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

10.

这是两个函数,一个是序列化,一个是反序列化

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {

    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if(root == null) return "[]";//当二叉树根节点都为Null,就直接返回一个空
        StringBuilder res = new StringBuilder("[");//序列化之后的序列
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        //层次遍历
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            if(node != null){//非空
                res.append(node.val+",");
                //我们直接要判定它是否存在左右节点,但这里因为Null也会被写入到输出结果中,所以就不需要去判定
                queue.add(node.left);
                queue.add(node.right);
            }else{
                res.append("null,");
            }
        }
        //将元素添加完毕后,别忘了右括号和删除最后一次添加的逗号
        res.deleteCharAt(res.length() - 1);//删除逗号
        res.append("]");
        return res.toString();
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if(data.equals("[]")) return null;
        data = data.substring(1,data.length()-1);//去括号
        String[] val = data.split(",");//分割
        TreeNode root = new TreeNode(Integer.parseInt(val[0]));
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int i = 1;//指向指针
        while(!queue.isEmpty()){
            TreeNode node = queue.poll();
            //左节点
            if(!val[i].equals("null")){//判定是否为空值,空节点
                node.left = new TreeNode(Integer.parseInt(val[i]));
                queue.add(node.left);
            }
            i++;
            //右节点
            if(!val[i].equals("null")){//判定是否为空值,空节点
                node.right = new TreeNode(Integer.parseInt(val[i]));
                queue.add(node.right);
            }
            i++;
        }
        return root;

    }
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));

这里用到了两个我不熟悉的方法,一个是stringbuilder的deleteCharAt(参数a):删除索引a的值,另一个是parseInt方法,parseInt(String s): 返回用十进制参数表示的整数值。就是将参数string类型的转化为Integer类型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值