二刷剑指offer

目录

1.二进制中1的个数

2.重建二叉树

3.二叉搜索树的后叉遍历序列

4.二叉树中和为某一值的路径

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

6.整数中1出现的次数

7.把数组排成最小的数

8.数组中的逆序对

9.两个链表的第一个公共节点

10.数字在排序数组中出现的次数

11.丑数

12.数组中只出现一次的数字

13.和为S的正数序列

14.左旋转字符串

15.求1+2+3+...n

16.数组中重复的数字

17.正则表达式匹配

18.表示数值的字符串


1.二进制中1的个数

分析一下代码: 这段小小的代码,很是巧妙。

如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

public class Solution {
    public int NumberOf1(int n) {
 //       int count=0;
 //       int flag=1;
 //       //flag不断左移,溢出int的最大范围后就会等于0
 //       while(flag!=0){
 //           if((n&flag)!=0){
 //               count++;
 //           }
 //           flag<<=1;
 //       }
 //      return count;
        
        
        //方法二:最优解:
        int count=0;
        while(n!=0){
            n=n&(n-1);
            count++;
        }
        return count;
    }
    
    
}

2.重建二叉树

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
         TreeNode node =reConstructBinaryTree(pre,0,pre.length-1,in,0,in.length-1);
        return node;
    }
    private TreeNode reConstructBinaryTree(int [] pre,int startPre,int endPre,int [] in,int startIn,int endIn){
        if(startPre>endPre||startIn>endIn)return null;
        TreeNode root=new TreeNode(pre[startPre]);   //每次都新建一个节点,若是第一个则是根节点,先序遍历数组的第一个元素为当前根节点
        for(int i=startIn;i<=endIn;i++){
            //找到在中序遍历中,根节点的下标位置
            if(in[i]==pre[startPre]){
                root.left=reConstructBinaryTree(pre,startPre+1,startPre+i-startIn,in,startIn,i-1);
                root.right=reConstructBinaryTree(pre,startPre+i-startIn+1,endPre,in,i+1,endIn);
                break;
            }
        }
        return root;
    }
}

3.二叉搜索树的后叉遍历序列

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length==0)return false;
        return judge(sequence,0,sequence.length-1);
    }
    
    private boolean judge(int []arr,int start,int end){
        if(start>=end)return true;
        int i=start;
        while(arr[i]

4.二叉树中和为某一值的路径

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

/*基本方法:
1.新建一个ArrayList里面每一个位置又可以存放一个ArrayList
2.再新建一个ArrayList,里面用于存放路径的val
3.如果①根为空②和为target并且左右节点都为空,那么代表当前list就是目标序列,将其添加进ArrayList
4.递归左右节点,到达左叶子节点后,删除当前叶子节点,去右叶子节点查找是否满足条件
5.直至返回到根节点,返回listAll,就是我们想要的结果
*/
public class Solution {
    private ArrayList> listAll=new ArrayList<>();
    private ArrayListlist=new ArrayList<>();
    public ArrayList> FindPath(TreeNode root,int target) {
        if(root == null)return listAll;
        target = target-root.val;
        list.add(root.val);
        if(target == 0&&root.right==null&&root.left==null){
            listAll.add(new ArrayList(list));
        }
        FindPath(root.left,target);
        FindPath(root.right,target);
        list.remove(list.size()-1);
         return listAll;
    }
}

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

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路3:如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。

在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

import java.util.HashMap;
//方法一:排序、取中间的数字、遍历一遍计算中间数字出现的次数、判断是否满足条件
//方法二:HashMap
//方法三:计数法
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        int len = array.length;
        if(len==0)return 0;
        int res = array[0];
        int count = 1;
        for(int i=1;ilen)return res;
        else return 0;
    }
}

6.整数中1出现的次数

题目描述

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)

 

方法:对每一位进行归纳,最后整合成一个数学表达式

public class Solution {
     public int NumberOf1Between1AndN_Solution(int n) {
         if(n <= 0)
             return 0;
         int count = 0;
         for(long i = 1; i <= n; i *= 10){
             long diviver = i * 10;          
             count += (n / diviver) * i + Math.min(Math.max(n % diviver - i + 1, 0), i);
        }
         return count;
     }
 }

https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion

7.把数组排成最小的数

题目描述

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

链接:https://www.nowcoder.com/questionTerminal/8fecd3f8ba334add803bf2a06af1b993?f=discussion
来源:牛客网

 * 解题思路:
 * 先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
 * 排序规则如下:
 * 若ab > ba 则 a > b,
 * 若ab < ba 则 a < b,
 * 若ab = ba 则 a = b;
 * 解释说明:
 * 比如 "3" < "31"但是 "331" > "313",所以要将二者拼接起来进行比较
public String PrintMinNumber(int [] numbers) {
        if(numbers == null || numbers.length == 0) return "";
        int len = numbers.length;
        String[] str = new String[len];
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < len; i++){
            str[i] = String.valueOf(numbers[i]);
        }
        Arrays.sort(str,new Comparator(){
            @Override
            public int compare(String s1, String s2) {
                String c1 = s1 + s2;
                String c2 = s2 + s1;
                return c1.compareTo(c2);
            }
        });
        for(int i = 0; i < len; i++){
            sb.append(str[i]);
        }
        return sb.toString();
    }

8.数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:

题目保证输入的数组中没有的相同的数字

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

示例1

输入

复制

1,2,3,4,5,6,7,0

输出

复制

7

解答:使用归并排序

public class Solution {
    int count = 0;
    public int InversePairs(int [] array) {
        mergerSort(array,0,array.length-1);
        return count%1000000007;
    }
    private void mergerSort(int[]arr,int l,int r){
        if(l>=r)return;
        int mid = l+(r-l)/2;
        mergerSort(arr,l,mid);
        mergerSort(arr,mid+1,r);
        merge(arr,l,r);
    }
    private int merge(int [] arr , int l, int r){
        int len = r-l+1;
        int[]copy = new int[len];
        for(int i =0;i=len){
                arr[j] = copy[lt];
                lt++;
            }
            else if(lt>mid){
                arr[j] = copy[rt];
                rt++;
            }
            //1 2 7 6 8 9
            else if(copy[lt]>copy[rt]){
                arr[j] = copy[rt];
                count=(count+mid-lt+1)%1000000007;
                rt++;
            }
            else{
                arr[j] = copy[lt];
                lt++;
            }
        }
        return count;
    }
}

9.两个链表的第一个公共节点

巧妙的解法:

1.第一个到达null的p一定是短链表,将其引用指向长链表头

2.在第二个p到达null的时候,上一个p在长链表已经先走了他们的差值

3.这样就避免了需要计算长度的操作,实际上是让长链表先走了差值步数。

public  static ListNode FindFirstCommonNode( ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1!=p2){
            p1 = (p1==null ? pHead2 : p1.next);
            p2 = (p2==null ? pHead1 : p2.next);
        }
        return p1;
    }

10.数字在排序数组中出现的次数

题目描述

统计一个数字在排序数组中出现的次数。

public class Solution {
    //方法一:一次遍历
    //方法二:二分搜索
    public int GetNumberOfK(int [] array , int k) {
        if(array.length==0)return 0;
        int l = BSL(array,k);
        int r = BSR(array,k);
        if(r==-1||l==-1)return 0;
        return  r-l+1;
    }
    
     // BinarySearch : 2 左边界
    public int BSL(int[]arr,int val){
        int l = 0;
        int r = arr.length;//前闭后开
        while(larr[mid]){
                l = mid+1;
            }
        }
        if (l == arr.length) return -1;//先判断val是不是大于所有的数
        return arr[l] == val ? (l) : -1;//再判断值是否相等
    }
 
    //BinarySearch : 3 右边界
    public int BSR(int [] arr,int val){
        int l = 0;
        int r = arr.length;//前闭后开
        while(larr[mid]){
                l = mid+1;
            }
        }
        if (l == 0) return -1;//先判断是否越界
        return arr[l-1] == val ? (l-1) : -1;//再判断值是否相等
    }

}

11.丑数

链接:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b?f=discussion
来源:牛客网
 

通俗易懂的解释:

首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方法会得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:

(1)丑数数组: 1

乘以2的队列:2

乘以3的队列:3

乘以5的队列:5

选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(2)丑数数组:1,2

乘以2的队列:4

乘以3的队列:3,6

乘以5的队列:5,10

选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(3)丑数数组:1,2,3

乘以2的队列:4,6

乘以3的队列:6,9

乘以5的队列:5,10,15

选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(4)丑数数组:1,2,3,4

乘以2的队列:6,8

乘以3的队列:6,9,12

乘以5的队列:5,10,15,20

选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(5)丑数数组:1,2,3,4,5

乘以2的队列:6,8,10,

乘以3的队列:6,9,12,15

乘以5的队列:10,15,20,25

选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;

……………………

疑问:

1.为什么分三个队列?

丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;

2.为什么比较三个队列头部最小的数放入丑数数组?

因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。

实现思路:

我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;

(1)1

|2

|3

|5

目前指针指向0,0,0,队列头arr[0] * 2 = 2,  arr[0] * 3 = 3,  arr[0] * 5 = 5

(2)1 2

2 |4

|3 6

|5 10

目前指针指向1,0,0,队列头arr[1] * 2 = 4,  arr[0] * 3 = 3, arr[0] * 5 = 5

(3)1 2 3

2| 4 6

3 |6 9

|5 10 15

目前指针指向1,1,0,队列头arr[1] * 2 = 4,  arr[1] * 3 = 6, arr[0] * 5 = 5

………………

import java.util.LinkedList;
/**
 * 别人的方法
 */
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if (index<=6)return index;
        int p1=0;
        int p2=0;
        int p3=0;
        LinkedListlist=new LinkedList<>();
        int minUrgly=1;
        list.add(minUrgly);
        while(list.size()

12.数组中只出现一次的数字

可以看一下博客:https://blog.csdn.net/weixin_42446419/article/details/97901846

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果

//方法一:使用hashMap[]存储数字出现的次数,遍历取出两个只出现一次的数字
//方法二:使用位亦或
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        int len = array.length;
        int result = 0;
        for(int a:array){
            result ^= a;
        }
        int first = findFirstBit(result);
        for(int a:array){
            if(isSameBit(a,first)){
                num1[0]^=a;
            }
            else{
                num2[0]^=a;
            }
        }
    }
    private int findFirstBit(int result){
        int count = 0;
        while(((result&1)==0)&&count<32){
            result>>=1;
            count++;
        }
        return count;
    }
    private boolean isSameBit(int num,int count){
        return ((num>>count)&1)==1;
    }
}

13.和为S的正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

链接:https://www.nowcoder.com/questionTerminal/c451a3fd84b64cb19485dad758a55ebe?f=discussion
来源:牛客网

//左神的思路,双指针问题
//当总和小于sum,大指针继续+
//否则小指针+
class Solution {
public:
    vector > FindContinuousSequence(int sum) {
        vector > allRes;
        int phigh = 2,plow = 1;
         
        while(phigh > plow){
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            if( cur < sum)
                phigh++;
             
            if( cur == sum){
                vector res;
                for(int i = plow; i<=phigh; i++)
                    res.push_back(i);
                allRes.push_back(res);
                plow++;
            }
             
            if(cur > sum)
                plow++;
        }
         
        return allRes;
    }
};

14.左旋转字符串

题目描述

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

//方法一:使用一个数组辅助存储:
//空间:O(k) 时间:O(n)

//方法二:整体reverse,反转前部分,反转后k部分
public class Solution {
    public String LeftRotateString(String str,int n) {
        if(str==null||str.length()==0)return "";
        n = n%str.length();
        StringBuilder stb = new StringBuilder(str);
        stb.reverse();
        StringBuilder stbB = new StringBuilder(stb.subSequence(0,stb.length()-n));
        stbB.reverse();
        StringBuilder stbA = new StringBuilder(stb.subSequence(stb.length()-n,stb.length()));
        stbA.reverse();
        stb = new StringBuilder();
        stb.append(stbB);
        stb.append(stbA);
        return stb.toString();
    }
}

15.求1+2+3+...n

题目描述

求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

public class Solution {
    public int Sum_Solution(int n) { 
        int sum = n;       
        boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);     
        return sum;    
    }
}

16.数组中重复的数字

巧妙解法:

题目描述

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

链接:https://www.nowcoder.com/questionTerminal/623a5ac0ea5b4e5f95552655361ae0a8?f=discussion
来源:牛客网
 

不需要额外的数组或者hash table来保存,题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。

 

代码是C:


int find_dup( int numbers[], int length) {

    for ( int i= 0 ; i= length) {

            index -= length;

        }   

        if (numbers[index] >= length) {

            return index;

        }   

        numbers[index] = numbers[index] + length;

    }   

    return - 1 ; 

}

不需要额外的空间消耗,时间效率是O(n)

17.正则表达式匹配


public class Solution {    
      public boolean match(char[] str, char[] pattern) {
        return ismMatch(0,str,0,pattern);
    }
    private boolean ismMatch(int strIndex,char[]str,int patternIndex ,char[]pattern){
        //递归终止条件一:strIndex和patternIndex同时等于各自数组长度
        if (strIndex==str.length&&patternIndex==pattern.length){
            return true;
        }
        //递归终止条件二:模式下标patternIndex先到达终点,表示匹配不成功
        if (patternIndex==pattern.length){
            return false;
        }

        // 特别注意,永远要判断下标,以防越界!!!

        //情况1:pattern[patternIndex+1]=='*',此时分为两种情况:
        // 1.1 pattern[patternIndex]==str[strIndex]||pattern[patternIndex]=='.' ,分三种情况,见下文
        // 1.2 pattern[patternIndex]!=str[strIndex],patternIndex + = 2 ,直接跳过当前字符,进行下一轮匹配
        if (patternIndex+1

18.表示数值的字符串

题目描述

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

class Solution {
    public:bool isNumeric(char* str) {    
        // 标记符号、小数点、e是否出现过 
        bool sign = false, decimal = false, hasE = false;     
        for (int i = 0; i < strlen(str); i++) {       
            if (str[i] == 'e' || str[i] == 'E') {         
                if (i == strlen(str)-1) return false; // e后面一定要接数字    
                if (hasE) return false;  // 不能同时存在两个e      
                hasE = true;        
            } else if (str[i] == '+' || str[i] == '-') {      
                // 第二次出现+-符号,则必须紧接在e之后          
                if (sign && str[i-1] != 'e' && str[i-1] != 'E') 
                    return false;            
                // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后  
                if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;       
                sign = true;     
            } else if (str[i] == '.') {    
                // e后面不能接小数点,小数点不能出现两次      
                if (hasE || decimal) return false;            
                decimal = true;        
            } else if (str[i] < '0' || str[i] > '9') // 不合法字符     
                return false;   
        }      
        return true;   
    }
};

19.滑动窗口的最大值

https://wiki.jikexueyuan.com/project/for-offer/question-sixty-five.html

你可能感兴趣的:(面试准备相关知识,算法学习)