剑指 Offer 题解摘录

本文深入探讨了一系列算法挑战,包括字符串转整数、查找数组中的重复数字、构建乘积数组、正则表达式匹配及判断字符串是否表示数值。通过对每道题目的详细分析与代码实现,展现了算法设计的多样性和复杂性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:把字符串转换成整数

将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。

这个解题算是考虑非常全面了,实现也很清晰,所以就摘录下来,以时时观摩。

package offer;

import java.util.Arrays;

public class Solution {

	public static boolean flag;
    public static int StrToInt(String str) {
    	//flag判断是因为输入字符串0返回0 还是错误情况下直接返回0
    	flag = false;
    	//判断输入是否合适
    	if(str == null || str.trim().equals("")) {
    		flag  = true;
    		return 0;
    	}
    	//symbol=0,说明该数为正数;symbol=1,该数为负数;
    	//start用来区分第一位是否为符号位
    	int symbol = 0;
    	int start = 0;
    	char[] chars = str.toCharArray();
    	if(chars[0] == '+') {
    		start = 1;
    	}else if(chars[0] == '-') {
    		start = 1;
    		symbol = 1;
    	}
    	int result = 0;
    	for(int i=start;i<chars.length;i++) {
    		if(chars[i] > '9' || chars[i] < 0) {
    			flag = true;
    			return 0;
    		}
    		int sum = result*10 + (int)(chars[i]-'0');
    		//举个例子,3*10+5=35,如果(35-5)/10不等于3,那么就产生了溢出
    		if((sum - (int)(chars[i]-'0'))/10 != result) {
    			flag = true;
    			return 0;
    		}
    		
    		result = result * 10 + (int)(chars[i]-'0');
    	}
    	result = (int) Math.pow(-1, symbol) * result;
    	return result;
    	
    }
    public static void main(String[] args) {
		String str = "8797899";
		System.out.println(StrToInt(str));
	}
}

题目描述:数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

这是我个人的解法,没考虑太多,就是i外循环控制每次开始比较的起点,i之前的是已经与后面的数组比较了,没有重复的。然后i向后移,作为到当前的比较数,一个一个地跟后面的比较。但最后提交时,确总是显示我的代码测试通过率为83.3%,让人头大。。。

public class Solution {

	public static boolean duplicate(int numbers[],int length,int [] duplication) {
        boolean flag = false;
        //代码1
        if(length <= 2) return false;
        int index = 0;
        for(int i = 0;i<length-1;i++){
            index = i;
            for(int j = i;j<length-1;j++){
                if(numbers[j+1] == numbers[index]){
                    duplication[0] = numbers[index];
                    flag = true;
                    return flag;
                }
            }
        }
        return flag;
    }
    public static void main(String[] args) {
		int[] chars = {2,3,1,0,2,5,3};
		int[] dupChar = new int[chars.length];
		System.out.println(duplicate(chars,chars.length,dupChar));
	}
}

后来添加了判断验证“一个长度为n的数组里的所有数字都在0到n-1的范围内”这个条件,代码得以通过,不过,这不是写在题目里了吗?我的理解是这个条件是默认的呢。。。好吧,题目就是上帝,自己多多考虑,写全面了,才是硬道理。于是在上面 代码1 处添加以下判别代码:

for(int i=0;i<length;i++){
            if(numbers[i] < 0 || numbers[i] > (length-1)) return flag;
        }

有一种所谓高级的方法,利用了哈希的特性,但不需要额外的存储空间。

主要算法如下:

  1. 把当前序列当成是一个下标和下标对应值时相同的数组;
  2. 遍历数组,判断当前位的值和下标是否相等;
  3. 若相等,则遍历下一位;若不等,则将当前位置i上的元素和a[i]位置上的元素比较;
  4. 若它们相等,则成功;若不等,则将它们俩交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标不一定对应;重复3的操作,知道当前位置i的值也为i,将i后移一位,再重复2。
链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8
来源:牛客网

public boolean duplicate(int array[],int length,int [] duplication) {
    if ( array==null ) return false;

    // 判断数组是否合法(每个数都在0~n-1之间)
    for ( int i=0; i<length; i++ ) {
        if ( array[i]<0 || array[i]>length-1 ) {
            return false;
        }
    }

    // key step
    for( int i=0; i<length; i++ ){
        while( i!=array[i] ){
            if ( array[i] == array[array[i]] ) {
                duplication[0] = array[i];
                return true;
            }

            int temp = array[i];
            array[i] = array[array[i]];
            array[array[i]] = temp;
        }
    }

    return false;
}

题目描述:构建乘积数组

给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

这道题是一个令人警醒的题目,理由如下:

  1. 审题不严;自己乍一看还以为是题目出错了,直接外循环套内循环B[i]=A[0]*A[1]*...*A[i-1]A[i]*A[i+1]*...*A[n-1]!!!
  2. 实现算法太简单,当然简单不是错,而是复杂度太高,一点技术含量也没有。
public class Solution {
    public int[] multiply(int[] A) {
        int length = A.length;
        int[] B = new int[length];
        if(length != 0 ){
            B[0] = 1;
            //计算下三角连乘
            for(int i = 1; i < length; i++){
                B[i] = B[i-1] * A[i-1];
            }
            int temp = 1;
            //计算上三角
            for(int j = length-2; j >= 0; j--){
                temp *= A[j+1];
                B[j] *= temp;
            }
        }
        return B;
    }
}

这个就有点档次了,但带来的问题就是,有点难以理解,按照矩阵里的说法就是,上三角,和下三角。当i为1的时候,下三角计算只有一个值,但上三角确是有n-1个值连乘。其实也可以这么理解,将一整个B[i]的连乘分为两步,第一步,先连乘下三角的部分,将处于下三角的部分的值连乘为目前的B[i]。然后再连乘上三角部分的值,这时就是下三角连乘过后的目前的B[i],依次乘以上三角的值,最后算完了,就是完整的B[i]。

从计算下三角和上三角的循环中我们就看到,下三角计算是已知B[0],i从1一直到n-1,即B[i]下三角元素全部乘了之后赋值B[i]。

从计算上三角和循环可以看到,是已知了B[n-1]肯定为1,然后从n-2一直到0,即B[i]上三角的元素全部乘了之后赋值B[i]。

题目描述:正则表达式匹配

请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。

感觉这道题的精髓在于,选择谁主动、谁被动,拿出谁去匹配谁。还有,就是把这些复杂的问题分分清楚,进行共性的抽象以及递归。

我个人做的方法,测试case通过率为6.67%,汗。。。分析自己主要是没把主要问题和次要问题,分分清楚。对于 if 和 else 以及各种逻辑的判断,没有把头绪理清楚,问题分类好。比如,对于匹配模式中的字符,有三种:

  • 普通字符
  • '.'
  • '*'

所有匹配字符中的组合,均是这三中或组合,具有实际意义,可以独立具有意义的罗列如下(普通字符以a代替):a  .  a*

即,所有的 '.' 和 a 都可以自己匹配字符,但对于 '*' 自己,却不具有任何意义,必须与 a 组合,且紧跟 a 之后,形成 'a*'。

那么问题就可以简化为两种了:

  • a 或 '.'
  • 'a*'

进一步地表达就是,pattern 的下一个字符是否为 '*'。那么对于匹配的算法就是:

  1. 字符串中拿出第一个字符和模式中的字符去匹配
  2. 如果pattern下一个字符不为 '*' ,那就比较简单。直接对当前字符进行匹配。如果匹配成功,继续匹配下一个;算法继续从1开始;匹配失败,则直接返回false。需要注意的是,匹配成功,有两种,一是字符相同;二是pattern中的当前字符为 '.' ;字符相同到没啥,但如果是第二种情况的话,还要考虑str中的边界问题。
  3. 如果如果pattern下一个字符是 '*' ,那就会稍微复杂一点,因为 '*' 可以代表0个或多个。
  4. 当 '*' 匹配0个字符时,即当前字符串字符与 '*' 前的字符不匹配,因此,str保持当前字符不变,pattern当前字符向后移动两位。注意:此时pattern指向的还是在 '*' 之前的字符上,没有匹配上,就把 'a*' 忽略掉,因此从目前的a上向后移动两位。
  5. 当 '*' 匹配一个或多个时,str当前字符向后移动一位,pattern当前字符不变。注意:这里呢,将匹配一个和多个看做同一种情况,因为,匹配一个是,str向后移动一位,此是pattern当前字符不变,情况跟上上一次相同,再移再相同。直到出现str当前字符与pattern字符不匹配,那么就进入4的情况了。
  6. 之后,就是撸代码了。
/**
 * 正则表达式匹配
 * 当模式中的第二个字符不是 '*' 时:
 * 1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的(递归)。
 * 2、如果字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。
 * 当模式中的第二个字符是 '*' 时:
 * 如果字符串的第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。
 * 如果字符串第一个字符跟模式第一个字符匹配,可以有3中匹配方式:
 * 1、模式向后移动2个字符,相当于 a* 被忽略。
 * 2、字符串向后移动1个字符,模式向后移动1个字符;
 * 3、字符串后移1个字符,模式不变,即继续匹配字符下一位,因为 * 可以匹配多位。
 *
 */
public class Solution {
    public boolean match(char[] str, char[] pattern) {
    if (str == null || pattern == null) {
        return false;
    }
    int strIndex = 0;
    int patternIndex = 0;
    return matchCore(str, strIndex, pattern, patternIndex);
}
  
public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
    //有效性检验:str到尾,pattern到尾,匹配成功
    if (strIndex == str.length && patternIndex == pattern.length) {
        return true;
    }
    //pattern先到尾,匹配失败
    if (strIndex != str.length && patternIndex == pattern.length) {
        return false;
    }
    //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位
    if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
        if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            return matchCore(str, strIndex, pattern, patternIndex + 2)//模式后移2,视为x*匹配0个字符
                    || matchCore(str, strIndex + 1, pattern, patternIndex + 2)//视为模式匹配1个字符
                    || matchCore(str, strIndex + 1, pattern, patternIndex);//*匹配1个,再匹配str中的下一个
        } else {
            return matchCore(str, strIndex, pattern, patternIndex + 2);
        }
    }
    //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
    if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) {
        return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
    }
    return false;
    }
}

题目描述:表示数值的字符串

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

先贴上自己的拙作,case通过率为6.67%。。。。。。

public class Solution {
    public boolean isNumeric(char[] str) {
        if(str.length == 0) return false;
        int start = 0;
        if(str[0] == '+' || str[0] == '-'){
            start = 1;
        }
        while(str[start] >= '0' && str[start] <= '9'){
            if(start == str.length-1) return true;
            start++;
        }
        if(str[start] == '.'){
            for(;start<str.length;start++){
                if(!(str[start] >= '0' && str[start] <= '9'))
                    return false;
            }
        }else if(str[start] == 'e' || str[start] == 'E'){
            if(start+1<str.length &&(str[start+1] == '+' || str[start+1] == '-')){
                start += 1;
            }
            for(;start<str.length;start++){
                if(!(str[start] >= '0' && str[start] <= '9'))
                    return false;
            }
           
        }else return false;
        
        return true;
    }
}

果真是if else到底型人才!!!

参考别人解法:

正则表达式解法

//正则表达式解法
public class Solution {
    public boolean isNumeric(char[] str) {
        String string = String.valueOf(str);
        return string.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");
    }
}
/*
以下对正则进行解释:
[\\+\\-]?            -> 正或负符号出现与否
\\d*                 -> 整数部分是否出现,如-.34 或 +3.34均符合
(\\.\\d+)?           -> 如果出现小数点,那么小数点后面必须有数字;
                        否则一起不出现
([eE][\\+\\-]?\\d+)? -> 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,
                        紧接着必须跟着整数;或者整个部分都不出现
*/

正常解答:

public class Solution {
    private int index = 0;
  
    public boolean isNumeric(char[] str) {
        if (str.length < 1)
            return false;
         
        boolean flag = scanInteger(str);
         
        if (index < str.length && str[index] == '.') {
            index++;
            flag = scanUnsignedInteger(str) || flag;
        }
         
        if (index < str.length && (str[index] == 'E' || str[index] == 'e')) {
            index++;
            flag = flag && scanInteger(str);
        }
         
        return flag && index == str.length;
         
    }
     
    private boolean scanInteger(char[] str) {
        if (index < str.length && (str[index] == '+' || str[index] == '-') )
            index++;
        return scanUnsignedInteger(str);
         
    }
     
    private boolean scanUnsignedInteger(char[] str) {
        int start = index;
        while (index < str.length && str[index] >= '0' && str[index] <= '9')
            index++;
        return start < index; //是否存在整数
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值