剑指Offer编程整理(五)

本文精选了11道经典的编程题目,覆盖了从简单的数学运算到复杂的数据结构与算法,如扑克牌顺子判断、圆圈中最后剩下的数、构建乘积数组等,每道题目都提供了详细的解题思路和代码实现。

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

1、扑克牌顺子

2、孩子们的游戏(圆圈中最后剩下的数)

3、求1+2+3+...+n

4、不用加减乘除做加法

5、把字符串转换成整数

6、数组中重复的数字

7、构建乘积数组

8、正则表达式匹配

9、表示数值的字符串

10、字符流中第一个不重复的字符

11、链表中环的入口结点


1、扑克牌顺子

(1)问题描述:

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。

(2)解题思路:

统计0的个数,看相邻的数空缺是否为0的个数。

(3)代码实现:

import java.util.Arrays;
public class Solution {
    public boolean isContinuous(int [] numbers) {

        if (numbers == null || numbers.length != 5){
            return false;
        }
        Arrays.sort(numbers);
        int numberOfZero = 0;
        int numberOfGap = 0;
        //统计0的个数
        for (int i=0 ; i<numbers.length&&numbers[i]==0;i++){
            numberOfZero++;
        }

        int small = numberOfZero;
        int big = small+1;

        //看相邻的数空缺是否为0的个数;
        while (big < numbers.length){
            if (numbers[small] == numbers[big]){
                return false;
            }
            numberOfGap += (numbers[big] - numbers[small]-1);
            small = big;
            big++;
        }

        return numberOfGap <= numberOfZero;
    }
}

2、孩子们的游戏(圆圈中最后剩下的数)

(1)问题描述:

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

(2)解题思路:

法一:数组实现,每删除第m个位置的值,得到一个新的数组(第m个位置后的值放在前面,第m个位置前的值放在后面),直到只剩最后一个值。

法二:定义一个循环链表,将总数添加到循环链表中;循环遍历链表,删除第m个结点;直到最后一个结点,返回该结点值。

(3)代码实现:

法一:

public int LastRemaining_Solution(int n, int m) {
         if(m == 0) return -1;

        int[] a = new int[n];
        for(int i=0; i<n; i++)
            a[i] = i;

        for(int i=0; i<n-1; i++){
            int len = a.length;
            int index = (m - 1) % len;

            int[] aux = new int[len-1];
            int j = 0;
            for(int k=index+1; k<len; k++)
                aux[j++] = a[k];
            for(int k=0; k<index; k++)
                aux[j++] = a[k];

            a = aux;
            aux = null;
        }

        return a[0];
    }

法二:

public int LastRemaining_Solution(int n, int m) {
        Node header = new Node(0);
        Node pointer = header;
        for (int i=1;i<n;i++){
            pointer.next = new Node(i);
            pointer = pointer.next;
        }
        pointer.next = header;
        while (pointer != pointer.next){
            for (int i=0;i<m-1;i++){
                pointer = pointer.next;
            }
            pointer.next = pointer.next.next;
        }
        return pointer.next.no;
    }

3、求1+2+3+...+n

(1)问题描述:

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

(2)解题思路:

递归

(3)代码实现:

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

4、不用加减乘除做加法

(1)问题描述:

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

(2)解题思路:

法一:计数法

法二:位运算

(3)代码实现:

法一:

public int Add(int num1,int num2) {
        while (num2!=0)
        {
            num1++;
            num2--;
        }
        return num1;
    }

法二:

public int add(int num1,int num2) {
        int Sum, Carry;
        do
        {
            Sum = num2 ^ num1;
            Carry = (num1 & num2) << 1;
            num2 = Carry;
            num1 = Sum;
        } while (num2 != 0);

        return num1;
    }

5、把字符串转换成整数

(1)问题描述:

将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0

(2)解题思路:

法一:从高位到低位

法二:从低位到高位

(3)代码实现:

法一:

public int StrToInt(String str) {
        if (str.length()==0 || str.equals("")){
            return 0;
        }
        char symble = str.charAt(0);
        int result = 0;
        if (symble >= '0' && symble <= '9'){
            result += symble - '0';
        }else if (!(symble == '-' || symble == '+')){
            return 0;
        }
        for (int i=1;i<str.length();i++){
            if (str.charAt(i)>='0' && str.charAt(i)<='9'){
                int value = str.charAt(i)-'0';
                result = result*10 + value;
            }else {
                return 0;
            }
        }

        return str.charAt(0)=='-'?-result:result;
    }

法二:

public int StrToInt(String str) {
        if(str==null || str.length() == 0){
            return 0;
        }
        int result = 0;
        char[] chs = str.toCharArray();
        int len = chs.length;
        for(int i=len-1, j=0; i>0; i--, j++){
            int c = (int)chs[i];
            if(c<48 ||c>57){
                return 0;
            }else{
                result += (c-48)*Math.pow(10, j);
            }
        }
        int c = (int)chs[0];
        if(c<=57&&c>=48){
            result += (c-48)*Math.pow(10, len-1);
        }
        if(result<-2147483648 || result>2147483647){
            return 0;     //越界,如果真的越界,直接会报错,result本身没办法越界
        }else if(str.equals("2147483648")){
            if(c == 45){
                result = -2147483648;     //边界值
            }
        }else if(str.equals("-2147483648")){
            result = -2147483648;         //边界值
        }else{
            if(c == 45){
                result = -result;         //负号处理
            }
        }
        return result;
    }

6、数组中重复的数字

(1)问题描述:

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

(2)解题思路:

法一:先将数组进行快排,再看相邻的两个数是否相等。

法二:构造一个容量为N的辅助数组B,原数组A中每个数对应B中下标,首次命中,B中对应元素+1。如果某次命中时,B中对应的不为0,说明,前边已经有一样数字了,那它就是重复的了。

法三:由于所有元素值是有范围的,因此可以用一个长度为n的数组,下标表示序列中的每一个值,下标对应的值表示该下标出现的次数。

    只需扫描一次原序列,就统计出所有元素出现的次数;

    再扫描一次哈希数组,找到一个出现次数大于1的值即可。

(3)代码实现:

法一:

public boolean duplicate(int numbers[],int length,int [] duplication) {
 
if(numbers == null || numbers.length == 0) return false;
        Arrays.sort(numbers);
        int flag = 0;//做标记
        for(int i=0;i<length-1;i++) {
            if(numbers[i] == numbers[i+1]) {
                duplication[0] = numbers[i];
                flag = 1;
                break;
            }
        }
        return flag == 1? true:false;
    }

法二:

 public boolean duplicate(int numbers[],int length,int [] duplication) {
        int[] assist = new int [length];
        for(int i = 0; i < length; i++){
            if(assist[numbers[i]] == 0){
                assist[numbers[i]] ++;
            }else{
                duplication[0] = numbers[i];
                return true;
            }
        }
        return false;
    }
法三:

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
    int[] hash = new int[length];
    for( int i=0; i<length; i++ ){
        hash[array[i]]++;
    }
    for(int i=0; i<length; i++){
        if ( hash[i]>1 ) {
            duplication[0] = i;
            return true;
        }
    }
    return false;
}

7、构建乘积数组

(1)问题描述:

给定一个数组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]。不能使用除法。

(2)解题思路:

计算前i-1个元素的乘积,及后n-i个元素的乘积, 分别保存在两个数组中。

(3)代码实现:

 public int[] multiply(int[] A) {

        int[] left = new int[A.length];
        left[0] = 1;
        for (int i = 1; i < left.length; i++) {
            left[i] = A[i-1]*left[i-1];
        }

        int[] right = new int[A.length];
        right[right.length-1] = 1;
        for (int i = right.length-2; i >=0; i--) {
            right[i] = A[i+1]*right[i+1];
        }

        int[] arr = new int[A.length];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = left[i]*right[i];
        }
        return arr;
    }

8、正则表达式匹配

(1)问题描述:

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

(2)解题思路:

一、如果模式串的下一个字符是*, 

1.1 并且模式串的当前字符能与主串的字符进行匹配,则可能出现三种情况:
1、模式串的当前字符匹配到0个字符,则主串不变,模式穿移动到两个字符
2、模式穿的当前字符匹配到1个字符,则主串移动一个位置,模式串移动两个位置
3、模式串的当前字符匹配到多个字符,则主串移动一个位置,模式串移动两个位置。 1.2 如果不能匹配的话: 主串不变,模式串移动两个位置;
二、如果下一个字符不是*,则进行逐个字符进行匹配 三、如果模式串的下一个字符是.,则就进行一个字符的匹配

(3)代码实现:

public class Solution {
    public boolean match(char[] str, char[] pattern) {
        if (str == null || pattern == null)
            return false;
        return matchRegCore(str, 0, str.length, pattern, 0, pattern.length);
    }

    private boolean matchRegCore(char[] str, int i, int length1,
            char[] pattern, int j, int length2) {
        if (i == length1 && j == length2) {
            // 主串匹配到末尾,模式串要么也匹配到末尾要么当前位置的字符是*,否则返回false
            if (j == length2 || pattern[j] == '*')
                return true;
            else
                return false;
        }
        if (i != length1 && j == length2)
            return false;
       
        if (j + 1 < length2 && pattern[j + 1] == '*') {
            if (i < length1 && (pattern[j] == str[i] || pattern[j] == '.')) {
                return matchRegCore(str, i + 1, length1, pattern, j, length2)
                        || matchRegCore(str, i + 1, length1, pattern, j + 2,
                                length2)
                        || matchRegCore(str, i, length1, pattern, j + 2,
                                length2);
            } else {
                return matchRegCore(str, i, length1, pattern, j + 2, length2);
            }
        }
        if (i < length1 && (str[i] == pattern[j] || pattern[j] == '.')) {
            return matchRegCore(str, i + 1, length1, pattern, j + 1, length2);
        }
        return false;
    }
}

9、表示数值的字符串

(1)问题描述:

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

(2)解题思路:

循序渐进

(3)代码实现:

public static boolean isNumeric(char[] str) {
        int point = 0;//记录小数点个数
        int i = 0;//遍历str数组用的下标
        int eFlag = 0;//e/E出现的次数
        //判断第一个字符是不是符号,是的话后续的判断就跳过一个数
        if(str[0] == '-' || str[0] == '+' ) {   
            i++;
        }
         
        for (; i<str.length; ++i) {
            if(str[i] == '+' || str[i] == '-'){
                //出现字符,若此字符的前一个字符不是e/E则认为是多出来的符号
                if (str[i-1] != 'e' && str[i-1] != 'E')
                    return false;
                continue;
            }
            
            if(str[i] == 'e' || str[i] == 'E') {
                eFlag++;
                if(eFlag > 1)//e/E不能出现两次
                    return false;
                //若e/E的前一个字符不是数字,或者e/E出现字符串的第一个或者最后一个,都认为是错的
                if(i-1<0 || str[i-1] <48 || str[i-1] >57 || i+1>str.length-1 )
                    return false;
                point++;
                continue;
            }
             
            if (str[i] == '.') {//小数点数字不能超过两个
                ++point;
                if (point > 1) {
                    return false;
                }
                continue;
            }
            //出现非数字且不是e/E则认为是错的(小数点和符号在前面的判断里用“continue”跳过了)
            if ((str[i] < 48 || str[i] > 57) && (str[i] != 'e') && (str[i] != 'E'))
                return false;
             
        }
       return true;
}
另:

public boolean isNumeric(char[] str) {
        String s = new String(str);
        if(s.matches("(-|\\+)?\\d*(\\.)?\\d*((E|e)(-|\\+)?\\d+)?")){
            return true;
        }
        return false;
    }

10、字符流中第一个不重复的字符

(1)问题描述:

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

(2)解题思路:

法一:用数组。

法二:用list。

(3)代码实现:

法一:

public class Solution {

    int[] cIndexes = new int[256];
    int index = 0;

    {
        for (int i = 0; i < cIndexes.length; i++){
            cIndexes[i] = -1;
        }
    }

    //Insert one char from stringstream
    public void Insert(char ch)
    {
        //插入数据,判断当前字符是否是第一次出现,是(为-1)则在对应的位置插入
        // 当前字符出现在字符流的位置,否则置为已经出现过(-2)
        if (cIndexes[ch] == -1){
            cIndexes[ch] = index;
        }else if (cIndexes[ch] >= 0){
            cIndexes[ch] = -2;
        }
        index ++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        //遍历每一个出现一次的字符,将index最小的值返回
        int min = Integer.MAX_VALUE;
        char minC = '#';
        for (int i = 0; i < cIndexes.length; i++){
            if (cIndexes[i] >= 0 && cIndexes[i] < min){
                min = cIndexes[i];
                minC = (char) i;
            }
        }

        return minC;
    }
}

法二:

public class FirstApperanceOnce {
    ArrayList<Character> list = new ArrayList<Character>();
    int[] counts = new int[128];
    //Insert one char from stringstream
    public void Insert(char ch) {
        if(counts[(int)ch] ==0){
            list.add(ch);
        }else if(list.contains(ch)){
            list.remove((Character)ch);
        }
        counts[(int)ch] = counts[(int)ch]+1;


    }
    //return the first appearence once char in current stringstream
    public char firstAppearingOnce() {
        return list.isEmpty()?'#':list.get(0);
    }
}

11、链表中环的入口结点

(1)问题描述:

一个链表中包含环,请找出该链表的环的入口结点。

(2)解题思路:

法一:使用ArrayList,遍历链表,将每个Node都存入List,如果list中存在该结点,则该节点为环中的入口。

法二:使用两个相邻的指针,断开每次的之前的节点,当前面的指针指向null时,后面的指针即为环的入口结点。

法三:快指针与慢指针。

(3)代码实现:

法一:

 public Node solutionWithList(Node pHead){
        ArrayList <Node> list=new ArrayList<Node>();
        list.add(pHead);
        Node curnode=pHead.next;
        if(curnode==null){
            return null;
        }
        while(!list.contains(curnode)){
            list.add(curnode);
            curnode=curnode.next;
        }
        return curnode;
    }

法二:

public Node EntryNodeOfLoop(Node pHead) {
        if(pHead.next == null){
            return null;
        }
        Node front = pHead.next;
        Node behind = pHead;
        while (front != null){
            behind.next = null;
            behind = front;
            front = front.next;
        }
        return behind;
    }

法三:

public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        ListNode fast = pHead;
         ListNode slow = pHead;
         while(fast != null && fast.next != null){
             fast = fast.next.next;
              slow = slow.next;
              //当快指针 与 慢指针相遇时
              if(fast == slow){
                  fast = pHead;
                 //再次相遇
                 while(fast != slow){
                     fast = fast.next;
                     slow = slow.next;
                 }
                 return fast;
             }
         }
         return null;
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值