《剑指offer》刷题(4)好题汇总

写写《剑指offer》里面比较新奇的解法(第一次刷题见过的)

Map

48.最长不含重复字符的子字符串

HashMap解法:

  • 重点解释这一句:i=Math.max(i,map.get(s.charAt(j))+1); 为什么要和i比较max
    • 防止i指针左移
    • 考虑 abba 这种情况,遍历到第二个a的时候map中 a:0,b:2,i=2,map.get(‘a’)+1=1,而此时i明显需要大于等于2,所以用一个max函限制一下i指针不可以左移
public int lengthOfLongestSubstring(String s) {
    int res=0;
    Map<Character,Integer> map=new HashMap<>();
    for(int j=0,i=0;j<s.length();j++){
        if(map.containsKey(s.charAt(j))){
            i=Math.max(i,map.get(s.charAt(j))+1);
            //map.containsKey(s.charAt(j))不知道包含的s.charAt(j)是在i指针前面还是后面
        }
        map.put(s.charAt(j),j);
        res=Math.max(res,j-i+1);
    }
    return res;
}

找规律

43. 1~n整数中1出现的次数

思路:递归

  • 如n=1234,high=1, pow=1000, last=234,可以将数字范围分成两部分:1-999和1000-1234
    • 1-999这个范围1的个数是f(pow-1)
    • 1000~1234这个范围1的个数需要分为两部分
      • 千分位是1的个数:千分位为1的个数刚好就是(last+1),注意,这儿只看千分位,不看其他位
      • 其他位是1的个数:即是234中出现1的个数,为f(last)
    • 所以全部加起来是f(pow-1) + last + 1 + f(last);
  • 例子如3234,high=3, pow=1000, last=234,可以将数字范围分成两部分1-999,1000-1999,2000-2999和3000-3234
    • 1-999这个范围1的个数是f(pow-1)
    • 1000~1999这个范围1的个数需要分为两部分:
      • 千分位是1的个数:千分位为1的个数刚好就是pow,注意,这儿只看千分位,不看其他位
      • 其他位是1的个数:即是999中出现1的个数,为f(pow-1)
    • 2000~2999这个范围1的个数是f(pow-1)
    • 3000~3234这个范围1的个数是f(last)
    • 所以全部加起来是pow + highf(pow-1) + f(last);

public int countDigitOne(int n) {
    return count(n);
}
private int count(int n){
    //n=high*pow+low
    if(n<1) return 0;
    String s=String.valueOf(n);
    int high=s.charAt(0)-'0';//最高位,分为是1和不是1的情况
    int pow=(int)Math.pow(10,s.length()-1);
    int low=n-high*pow;
    if(high==1){
        return count(pow-1)+count(low)+1+low;
    }else{
        return count(pow-1)*high+pow+count(low);
    }
}

44. 数字序列中某一位的数字

思路:

  • 确定 n 所在 数字 的 位数 ,记为 digit;
  • 确定 n 所在的 数字 ,记为 num ;
  • 确定 n 是 num 中的哪一数位,并返回结果。
public int findNthDigit(int n){
	int digit=1;
	long start=1;
	long count=9;
	while(n>count){
		n-=count;
		digit+=1;
		start*=10;
		count=digit*start*9;
	}
	//此时n是从start开始计数的,位数是digit
	long num=start+(n-1)/dight;
	return Long.toString(num).charAt((n-1)%digit)-'0';
}

61.扑克牌中的顺子

思路:

  • 不能有重复
  • 将5个数排序,统计0的个数,且第五张数字的值减去第一张非0的牌数字的值小于5,因为大于等于5无论怎么用0填补也无法凑成顺子
  • 一个for搞定
public boolean isStraight(int[] nums) {
    Arrays.sort(nums);
    int joker=0;
    for(int i=0;i<=3;i++){
        if(nums[i]==0){
            joker++;
        }else if(nums[i]==nums[i+1]){
            return false;
        }
    }
    return nums[4]-nums[joker]<5;
}

约瑟夫环

62. 圆圈中最后剩下的数字

public int lastRemaining(int n, int m) {
    //(0+m)%上一轮长度
    int ans=0;
    for (int i=2;i<=n;i++){
        ans=(ans+m)%i;
    }
    return ans;
}

状态机

56 - I. 数组中数字出现的次数

题目:一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路:

  1. 数组所有数字做异或操作,其结果等于这两个不同的数字做异或操作,且结果一定不为0,故我们只要找到这两个数在某一位的不同就可以区分开来
  2. 找最后一位1的操作是mask=x&(-x)
public int[] singleNumbers(int[] nums) {
    int xor = nums[0];
    for(int i=1;i<nums.length;i++){
        xor ^= nums[i];
    }
    int lastOnePosition = xor & (-xor);
    int ans1 = 0,ans2 = 0;
    for(int num :nums){
        if((num&lastOnePosition) == lastOnePosition){
            ans1 ^= num;
        }else{
            ans2 ^= num;
        }
    }
    return new int[]{ans1^0 ,ans2^0};
}

56 - II. 数组中数字出现的次数(难)

Krahets大神解法
题目:在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
思路:有限状态自动机,由于二进制只能表示 0, 1 ,因此需要使用两个二进制位来表示 3 个状态。设此两位分别为 two , one,状态转变为 00->01->10->00…

public int singleNumber(int[] nums) {
    int ones = 0, twos = 0;
    for(int num : nums){
        ones = ones ^ num & ~twos;
        twos = twos ^ num & ~ones;
    }
    return ones;
}

贪心算法

14- II. 剪绳子 II

public int cuttingRope(int n) {
    if (n < 4) {
        return n - 1;
    }
    int mod = (int) 1e9 + 7;
    long res = 1;
    while (n > 4) {
        res *= 3;
        res %= mod;
        n -= 3;
    }
    return (int) (res * n % mod);
}

位运算

16.二进制中1的个数

n&(n−1) 解析: 二进制数字 n最右边的 1 变成 0 ,其余不变


public int hammingWeight_best(int n) {
    int res = 0;
    while(n!=0){
        n&=(n-1);
        res++;
    }
    return res;
}

31. 栈的压入、弹出序列

不断用popped数组的值和栈顶比较即可,就提一个点:stack在pop的时候一定要检查是否为空!

public boolean validateStackSequences(int[] pushed, int[] popped) {
    Stack<Integer> stack = new Stack<>();
    int index = 0;
    for (int i = 0; i < pushed.length; i++) {
        stack.push(pushed[i]);
        //pop一定要判断stack非空
        while (!stack.isEmpty() && index < popped.length && stack.peek() == popped[index]) {
            stack.pop();
            index++;
        }
    }
    return stack.isEmpty();
}

33. 判断数组是否为二叉搜索树的后序遍历序列(难)

思路:辅助单调栈(借助后序遍历倒序)

  1. 后序遍历顺序为 “左、右、根”
  2. 后序遍历倒序类似先序遍历的镜像 ,即先序遍历为 “根、左、右” 的顺序,而后序遍历的倒序为 “根、右、左” 顺序。
public boolean verifyPostorder(int[] postorder) {
    Stack<Integer> stack=new Stack<>();//单调栈,存放递增序列
    int root=Integer.MAX_VALUE;//把root看成MAX_VALUE的左子树
    for(int i=postorder.length-1;i>=0;i--){
        if(postorder[i]>root) return false;
        while (!stack.isEmpty()&&stack.peek()>postorder[i]){
            root=stack.pop();
            //这里是查找postorder[i]的父节点root,因为root是单调栈中大于postorder[i]的最近的一个值
        }
        stack.push(postorder[i]);
    }
    return true;
}

快速幂

16.数值的整数次方

public static double myPow(double x, int n){
    if(n==0) return 0;
    long b = n;//防止取n=-n溢出
    if(b<0){
        b=-b;
        x=1/x;
    }
    double res=1.0;
    while(b>0){
        //判断奇数
        if((b&1)==1){
            res*=x;
        }
        x*=x;
        b>>=1;
    }
    return res;
}

17.打印从1到最大的n位数(重点)

法一:不考虑大数问题的版本,自己实现一个快速幂

public int[] printNumbers(int n) {
    int size=mypow(10,n)-1;
    int[] res=new int[size];
    for(int i=0;i<size;i++){
        res[i]=i+1;
    }
    return res;

}
public int mypow(int base,int n){
    if(base==0) return 0;
    int res=1;
    while(n>0){
        if((n&1)==1)
            res*=base;
        base*=base;
        n>>=1;
    }
    return res;
}

法二:考虑大数问题,用字符串来做(推荐)

//n可能非常大,需要char数组来表示
public void print1ToMaxOfNDigits(int n) {
    if (n < 0) return;
    char[] number = new char[n];
    print1ToMaxOfNDigits(number, 0);
}

private void print1ToMaxOfNDigits(char[] number, int n) {
    if (n == number.length) {
        printNumber(number);
        return;
    }
    for (int i = 0; i < 10; i++) {
        number[n] = (char) (i + '0');
        print1ToMaxOfNDigits(number, n + 1);
    }
}

private void printNumber(char[] number) {
	String a = String.valueOf(number);
	int n = Integer.valueOf(a);
	System.out.println(n);
	/*
    int index = 0;
    while (index < number.length && number[index] == '0') {
        index++;
    }
    while (index < number.length) {
        System.out.print(number[index++]);
    }
   
    System.out.println();
     */
}

数值字符串正则问题

20. 表示数值的字符串

标志位判断法,推荐

public boolean isNumber(String s) {
    if(s==null || s.length()==0) return false;
    boolean numSeen=false;
    boolean dotSeen=false;
    boolean eSeen=false;
    char[] c=s.trim().toCharArray();
    for (int i = 0; i < c.length; i++) {
        if(c[i]>='0' && c[i]<='9'){
            numSeen=true;
        }else if(c[i]=='.'){
            if(dotSeen || eSeen){
                return false;
            }
            dotSeen=true;
        }else if(c[i]=='e'||c[i]=='E'){
            if(eSeen||!numSeen){
                return false;
            }
            eSeen=true;
            numSeen=false;//重置,确保e之后出现数字
        }else if(c[i]=='+'||c[i]=='-'){
            if(i!=0 && c[i-1]!='e'&&c[i-1]!='E'){
                return false;
            }
        }else {
            return false;
        }
    }
    return numSeen;
}

67. 把字符串转换成整数

  • 符号位: 三种情况,即 ‘’++’’ , ‘’-−’’ , ''无符号" ;新建一个变量保存符号位,返回前判断正负即可。
  • 非数字字符: 遇到首个非数字的字符时,应立即返回。
  • 在每轮数字拼接前,判断 res 在此轮拼接后是否超过 2147483647,若超过则加上符号位直接返回。设数字拼接边界 bndry = 2147483647 // 10 = 214748364,则以下两种情况越界:
    • res>bndry ,执行拼接10×res≥2147483650越界
    • res=bndry,x>7
public static int strToInt(String str) {
    char[] c = str.trim().toCharArray();
    if (c.length == 0) return 0;
    int sign = 1;//正负标志位
    int res = 0;
    int i = 1;
    int boundary = Integer.MAX_VALUE / 10;
    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;
        if (res > boundary || res == boundary && c[j] > '7') {//对于正数
            return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
        }
        res = res * 10 + c[j] - '0';
    }
    return sign * res;
}

摩尔投票法

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

public int majorityElement(int[] nums) {
    /*摩尔投票法,时间O(N),空间O(1)*/
    int x = 0;//假设的众数
    int votes = 0;
    for(int num : nums){
        if(votes == 0) x = num;
        votes += num == x ?  1 : -1;
    }
    return x;
}

快排

40. 最小的k个数

快排或者大根堆(每次弹出最大的数)
在面试中,另一个常常问的问题就是这两种方法有何优劣。看起来分治法的快速选择算法的时间、空间复杂度都优于使用堆的方法,但是要注意到快速选择算法的几点局限性:
第一,算法需要修改原数组,如果原数组不能修改的话,还需要拷贝一份数组,空间复杂度就上去了。
第二,算法需要保存所有的数据。如果把数据看成输入流的话,使用堆的方法是来一个处理一个,不需要保存数据,只需要保存 k 个元素的最大堆。而快速选择的方法需要先保存下来所有的数据,再运行算法。当数据量非常大的时候,甚至内存都放不下的时候,就麻烦了。所以当数据量大的时候还是用基于堆的方法比较好。

//快排变形
public int[] getLeastNumbers(int[] arr, int k) {
    if(arr.length==0||k<1) return new int[0];
    return quickSearch(arr,0,arr.length-1,k-1);
}
private int[] quickSearch(int[] arr,int lo,int hi,int k){
    int m=partition(arr,lo,hi);
    if(m==k){
        return Arrays.copyOf(arr,k+1);
    }else if(m>k){
        return quickSearch(arr,lo,m-1,k);
    }else{
        return quickSearch(arr,m+1,hi,k);
    }
}
private int partition(int[] arr,int lo,int hi){
    int i=lo,j=hi+1;
    int v=arr[lo];
    while(true){
        while(++i<hi&&arr[i]<v);
        while(--j>lo&&arr[j]>v);
        if(i>=j) break;
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
    arr[lo]=arr[j];
    arr[j]=v;
    return j;
}
public int[] getLeastNumbers(int[] arr, int k){
    if(arr.length==0 || k==0){
        return new int[0];
    }
    Queue<Integer> queue = new PriorityQueue<>((v1,v2)->v2-v1);//改成大根堆需要重写一下比较器
    for(int num:arr){
        if(queue.size()<k){
            queue.offer(num);
        }else {
            if(num<queue.peek()){
                queue.poll();
                queue.offer(num);
            }
        }
    }
    int[] res = new int[queue.size()];
    int index=0;
    for(int num:queue){
        res[index++]=num;
    }
    return res;
}

45. 把数组排成最小的数

数组转化为String数组,通过String的拼接来排序

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值