• 剑指Offer面试题解总结11-20


    剑指Offer(11~20)

    旋转数组中的最小数字

    题链
    题解:经典二分.我们可以将数组中的数字分成两部分,其中每一部分都是升序的,且靠前的一部分中的任意一个元素大于靠后的一部分中的任意一个元素.因此我们可以得出要找的元素是靠后部分的起始元素.
    因此我们得到二分策略:每次和数组最右边的元素比较,当查找的元素比右边元素小,r=mid;当查找的元素比右边元素大,l=mid+1;当查找的元素和右边元素相等,r–,之所以r–是因为数组中存在重复元素,因此我们无法判断最小元素是在左侧还是右侧,但能决定的是r下标的元素一定不是最小元素.

    	public int minArray(int[] numbers) {
            int n = numbers.length;
            int l = 0,r = n- 1;
            while(l < r){
                int mid = l + r >> 1;
                if(numbers[mid] < numbers[r]){
                    r = mid;
                }else if(numbers[mid] > numbers[r]){
                    l = mid + 1;
                }else{
                    r--;
                }
            }
            return numbers[l];
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    矩阵中的路径

    题链
    题解:典型的dfs模板题.从每个元素开始寻找,如果找到了则返回true,如果所有节点都找不到则返回false.

        static int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
        boolean[][] isV;
        char[][] b;
        int m, n;
        String key;
        boolean ans = false;
    
        public boolean exist(char[][] board, String word) {
            b = board;
            m = b.length;
            n = b[0].length;
            key = word;
            isV = new boolean[m][n];
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (b[i][j] == key.charAt(0)) {
                        isV[i][j] = true;
                        dfs(i, j, 1);
                        isV[i][j] = false;
                    }
                    if (ans) {
                        return true;
                    }
                }
            }
            return false;
        }
    
        public void dfs(int x, int y, int index) {
            if (index == key.length()) {
                ans = true;
                return;
            }
            if(ans){
                return;
            }
            for (int l = 0; l < 4; l++) {
                int nx = x + dir[l][0];
                int ny = y + dir[l][1];
                if (nx < 0 || nx >= m || ny < 0 || ny >= n || isV[nx][ny]) {
                    continue;
                }
                if (b[nx][ny] == key.charAt(index)) {
                    isV[nx][ny] = true;
                    dfs(nx, ny, index + 1);
                    isV[nx][ny] = false;
                }
               if(ans){
                   return;
               }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    剪绳子

    题链
    题解1:dp.设f[i]为长度为i的绳子分段得到的最大乘积,则f[i] = max(f[i],f[j]*(i-j),j*(i-j)),这里有一个坑要不仅要考虑f[j]*(i-j),还要考虑j(i-j)的大小,因为在计算f[j]的最大值时是没有考虑j的,而在计算f[i]时是要考虑j的,否则得到的结果都是0.

    	 public int cuttingRope(int n) {
            int[] f = new int[n+1];
            for(int i = 2; i <= n; i++){
                int max = 0;
                for(int j = 1; j < i; j++){
                    max = Math.max(max,Math.max(j*(i-j),(i-j) * f[j]));
                }
                f[i] = max;
            }
            return f[n];
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    题解2:数学.对应一个正整数n,根据不等式原理,当把n尽可能均分后乘积的值会更大.所以我们尝试将n分为2~n份,其中每份中有k1个m和k2个(m+1),然后分别求幂再相乘求最大值.

    	public int cuttingRope(int n) {
            int ans = 0;
            for(int i = 2; i <= n; i++){
                int r = n % i;
                int c = n / i;
                int x = c+1;
                ans = Math.max(ans,(int)Math.pow(c,i-r)*(int)Math.pow(x,r));
            }
            return ans;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    剪绳子II

    题链
    题解:此题和剪绳子I相比多的只是n的范围以及要对最终结果求余.但就是求余使得该题无法像剪绳子I一样枚举最大值.因为要求余,所以原来的最大值求余后得到的结果不一定还是最大值.
    因此要用数学去推导,利用不等式和求导求极值得出将n尽可能多的分成k个3后的乘积最大,当最终分完剩下的n为2,3,4时停止继续分.因为4 > 3 * 1,3 > 2 *1,2 > 1 * 1.

     static int MOD = (int)1e9+7;
        public int cuttingRope(int n) {
              if(n <= 3) return n - 1;
            long res=1L;
            int p=(int)1e9+7;
            while(n>4){
                res=res*3%p;
                n-=3;
            }
            //出来循环只有三种情况,分别是n=2、3、4
            return (int)(res*n%p);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    二进制中1的个数

    题链
    题解:此题又被称为汉明重量.当然可以用位运算左移统计每一位上1的个数.但此题还有时间复杂度更低的算法,就是构造巧妙的十六进制数分别进行1,2,4,8,16位上1的个数.

     public int hammingWeight(int n) {
             n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
            //得到值的每四位转为十进制相加得到数就是结果
            n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
            n = (n & 0x0F0F0F0F) + ((n >> 4) & 0x0F0F0F0F);
            n = (n & 0x00FF00FF) + ((n >> 8) & 0x00FF00FF);
            n = (n & 0x0000FFFF) + ((n >> 16) & 0x0000FFFF);
            return n;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    数值的整数次方

    题链
    题解:首先本题的主要思路是利用位运算实现快速乘法,然后还需要根据n去分类讨论,当n小于0最终的结果需要求倒数,且要实现将n变为-n,当n等于Integer.MIN_VALUE时,要把n变成Integer.MAX_VALUE之后最终再多乘一次.
    快速乘法就是利用去考虑n的二进制上每一位是1还是0,如果是1则进行相应的乘法,每次都需要将因子进行一次平方运算.举一个例子,3的5次方,5的二进制表示为101,所以第一次得到二进制位为1,所以结果ans要乘3,然后因子变成33=9;然后第二次得到二进制位为0,不对结果更新,然后因子变成99=81;然后第三次得到二进制位数位1,对结果相乘381=243,然后更新因子的值8181,然后退出循环得到结果243.

    public double myPow(double x, int n) {
            double curX = x;
            double ans = 1.0;
            boolean flag = true;
            boolean min = false;
            if(n < 0){
                if(n == Integer.MIN_VALUE){
                    n = Integer.MAX_VALUE;
                    min = true;
                }else {
                    n = -n;
                }
                flag = false;
            }
            for(int i = n; i > 0; i >>= 1){
                if(i % 2 == 1){
                    ans = ans * x;
                }
                x = x * x;
            }
            if(!flag){
                if(min){
                    return 1/(ans * curX);
                }
                return 1/ans;
            }
            return ans;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    打印从1到最大的n位数

    题链
    题解:…不说了

    	public int[] printNumbers(int n) {
              int end = (int)Math.pow(10,n);
            int[] ans = new int[end-1];
            for(int i = 1; i < end ;i++){
                ans[i-1] = i;
            }
            return ans;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    删除链表的节点

    题链
    题解:典型的链表题.首先根据值找到待删除节点,在找的过程中同样要找待删除节点的前驱结点,方便后续删除.在删除的时候,判断一下前驱结点是否为空,如果为空,直接返回head.next,否则,prev,next = key.next

     public ListNode deleteNode(ListNode head, int val) {
            if(head == null){
                return null;
            }
            ListNode cur = head;
            ListNode prev = null;
            while(cur != null){
                if(cur.val == val){
                    break;
                }
                prev = cur;
                cur = cur.next;
            }
            if(prev == null){
                return head.next;
            }
            prev.next = cur.next;
            return head;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    正则表达式匹配

    题链
    题解:典型的dp.首先记录f[i][j]表示s的前i个字符和t的前j个字符是否能够匹配.然后构造辅助函数match(char a,char b)来判断s[i]和t[j]是否匹配,如果匹配,f[i][j] |= f[i-1][j-1].如果不匹配,当t[j] = '*'时,分两种情况讨论,第一种该情况不匹配前面的那个字符,则f[i][j] |= f[i][j-2],如果匹配,说明match(s[i],t[j-1]])是true,f[i][j] |= f[i-1][j](因为s[i]是一定被匹配的,所以将f[i][j]交给f[i-1][j]).

    	public boolean isMatch(String s, String p) {
            if(s == null || p == null){
                if(s == null && p == null){
                    return true;
                }else{
                    return false;
                }
            }
            int m = s.length();
            int n = p.length();
            boolean[][] f = new boolean[m+1][n+1];
            f[0][0] = true;
            for(int i = 2; i <= n; i++){
                if(p.charAt(i-1) == '*'){
                    f[0][i] = f[0][i-2];
                }
            }
            for(int i = 1; i <= m; i++){
                for(int j = 1; j <= n; j++){
                    char ch1 = s.charAt(i-1);
                    char ch2 = p.charAt(j-1);
                    if(isV(ch1,ch2)){
                        f[i][j] = f[i-1][j-1];
                    }else if(ch2 == '*'){
                        f[i][j] = f[i][j-2];
                        if(isV(ch1,p.charAt(j-2))){
                            f[i][j] |= f[i-1][j];
                        }
                    }
                }
            }
            return f[m][n];
        }
        public boolean isV(char ch1,char ch2){
            return (ch1 == ch2 || ch2  == '.');
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    表示数值的字符串

    题链
    题解:这道题没有啥思维难度,主要是对细节的考察,设计的判断程序要适用于所有情况.
    首先去除掉字符串中首尾的空格.然后判断字符串中是否出现出e/E之外的字母或多个e/E,如果出现直接返回false;然后判断是否存在e/E,如果不存在,则对整个数字进行判断是否满足小数或整数;如果存在,则以e/E为分割点分割成两个数字,对前面的数字进行小数或整数的验证,对后面的整数进行整数的验证.
    小数的验证:首先开头部分存在0或1个符号位,如果有多个,则返回false,如果有符号位去掉符文位.然后判断是否有’.‘,如果不存在返回false,然后以’.‘将数字分割成两部分,前面的可以是没有或进行整数验证,后面的可以是没有或进行整数验证(整数不能含有符号位),但’.'前后不能都没有数字.
    整数的验证:首先开头部分存在0或1个符号文,如果有多个,则返回false.然后从符号位之后的进行数字验证.

    	public boolean isNumber(String s) {
            int n = s.length();
            int st = 0;
            int en = n-1;
            while(st < n){
                if(s.charAt(st) == ' '){
                    st++;
                }else{
                    break;
                }
            }
            while(en >= 0){
                if(s.charAt(en) == ' '){
                    en--;
                }else{
                    break;
                }
            }
            if(st > en){
                return false;
            }
            s = s.substring(st,en+1);
            n = s.length();
            for(int i = 0; i < n; i++){
                if(s.charAt(i) == ' '){
                    return false;
                }
                 if((s.charAt(i) >= 'a' && s.charAt(i) <= 'z' && s.charAt(i) != 'e')
                || s.charAt(i) >= 'A' && s.charAt(i) <= 'Z' && s.charAt(i) != 'E'){
                    return false;
                }
            }
            int cnt = 0;
            int index = 0;
            for(int i = 0; i < n; i++){
                if(s.charAt(i) == 'e' || s.charAt(i) == 'E'){
                    index = i;
                    cnt++;
                }
                if(cnt > 1){
                    return false;
                }
            }
            if(cnt == 1){
                String s1 = s.substring(0,index);
                String s2 = s.substring(index+1,n);
                if(s2.length() == 0){
                    return false;
                }
                if(isXi(s1) || isZh(s1)){
                    return isZh(s2);
                }else{
                    return false;
                }
            }else{
                if(isZh(s) || isXi(s)){
                    return true;
                }else{
                    return false;
                }
            }
    
        }
        public String removeFu(String s){
            if(s.length() == 0){
                return s;
            }
            if(s.charAt(0) == '+' || s.charAt(0) == '-'){
                return s.substring(1);
            }
            return s;
        }
        public boolean isZh(String s){
            s = removeFu(s);
            int n = s.length();
            if(n == 0){
                return false;
            }
            for(int i = 0; i < n; i++){
                if(!(s.charAt(i) >= '0' && s.charAt(i) <= '9')){
                    return false;
                }
            }
            return true;
        }
        public boolean isXi(String s){
            s = removeFu(s);
            int n = s.length();
            if(n == 0){
                return false;
            }
            int cnt = 0;
            int index = 0;
            for(int i = 0; i < n; i++){
                if(s.charAt(i) == '.'){
                    cnt++;
                    index = i;
                }
                if(cnt > 1){
                    return false;
                }
            }
             if(cnt == 1) {
                String s1 = s.substring(0, index);
                String s2 = s.substring(index + 1, n);
                if (isZh(s1) || isZh(s2)) {
                    return !(s2.contains("+") || s2.contains("-"));
                }else{
                    return false;
                }
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
  • 相关阅读:
    【深度学习实验】网络优化与正则化(三):随机梯度下降的改进——Adam算法详解(Adam≈梯度方向优化Momentum+自适应学习率RMSprop)
    LeetCode每日一练 —— OR36 链表的回文结构
    uniapp/小程序 onload方法每次打开页面都执行解读
    抓包day1
    Java高并发编程实战2,原子性、可见性、有序性,傻傻分不清
    牛客编程题--必刷101之字符串(高效刷题,举一反三)
    《痞子衡嵌入式半月刊》 第 63 期
    RocketMQ中Broker接收消息流程代码解析
    【C++】STL——priority_queue的使用及模拟实现
    EF core 的一些理解2
  • 原文地址:https://blog.csdn.net/weixin_52477733/article/details/126185716