剑指offer(十四)

44. 反转单词顺序列

Fish每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?

思路:用split分割,之后逐个交换字符串数组头尾或者倒序加入到StringBuffer。注意空格即可。

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Solution {
    public String ReverseSentence(String str) {
        if(str.trim().length() == 0) return str;
        String[] strlist = str.split(" ");
        int i = 0, j = strlist.length - 1;
        while(i < j){
            String tmp = strlist[j];
            strlist[j--] = strlist[i];
            strlist[i++] = tmp;
        }
        String res = "";
        for(int k = 0; k < strlist.length; k++){
            if(k == 0) res += strlist[k];
            else res += " " + strlist[k];
        }
        return res;
    }
}

45. 扑克牌顺子

判断抽到的5张牌是否为顺子,该副牌满足:大小王能充当任意数字,大小王一共4张,总牌数56张,A=1,J=11,Q=12,K=12,大小王=0。以下为原题。

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。

思路: 五张牌是顺子,必须满足最大值减最小值小于5,且每张牌不重复。注意一些限制条件,即每张牌数值范围是0-13,0的出现次数不能超过4次。另一种思路计算每张牌之间的间隔之和,若出现0的次数大于间隔之和,则为顺子,这种解法需要排序,略麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public boolean isContinuous(int [] numbers) {
        if(numbers.length == 0) return false;
        int min = 14, max = -1;
        int[] count = new int[14];
        for(int num : numbers){
            if(num == 0) {
                count[num] += 1;
                if(count[num] > 4) return false;
                continue;
            }
            if(num > 13 || num < 0) return false;
            
            count[num] += 1;
            if(count[num] > 1) return false;
            if(num < min) min = num;
            if(num > max) max = num;
        }
        if(max - min < 5) return true;
        return false;
    }

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

约瑟夫环:n个人排成一个圈,编号0,1,……n-1,从数字0开始报数,每次报m-1的人出列,且下一个人重新从0开始报数。求剩下的最后一个人的编号。以下是原题。

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

思路: 最简单的办法就是模拟上述过程,利用循环输出,这样的复杂度是O(mn)。

另一种,利用归纳的思想。第一轮报到m-1的人,显然其编号为m-1,但是考虑到m可能大于n,且所有人循环报数,因此我们对m-1与n作取余运算。

即第一轮出列的人的编号为(m-1) % n;

第二轮的人重新报数,报数0的人实际是第一轮中的m号,报数1的人实际是第一轮中的m+1号……报数为的人i是第一轮中的第m+i号,但是因为循环的存在,当m+i超过n的时候,实际是第一轮中的(m+i)% n号。

举例:编号=[0 1 2 3 4 5],n =6,m=3,

第一轮出列2;

第二轮,暂不考虑出列,先摸清规律,

3 报数 0,3的实际编号是m+0 = 3;

4 报数 1,4的实际编号是m + 1 = 4;

5 报数 2,5的实际编号是m + 2 = 5;

0 报数 3,0的实际编号是(m + 3)% n = 0

当m + i 小于 n的时候,(m + i)% n = m + i

因此可以说第二轮报数的实际编号就是(m + i)% n。

得到两轮之间编号与报数的递推之后,我们就可以将问题转换为子问题,即假定在第一轮出列了1个人之后,现在的n - 1个人循环报数,剩下的最后一个人报数为$f(n - 1, m)$,那么这个人在第一轮的实际编号$f(n, m) = ( m + f(n - 1, m) ) \% n$,这个代码就很好写了,直接递归求解,n为1的时候,显然编号为0,因此循环了n次,复杂度为O(n)。

1
2
3
4
5
    public int LastRemaining_Solution(int n, int m) {
        if(n == 0) return -1;
        if(n == 1) return 0;
        else return((LastRemaining_Solution(n - 1, m) + m) % n);
    }

47. 求1+2+3+…+n

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

思路: 去掉这些语句,能用的还有递归、逻辑运算、位运算和语言本身的一些数据结构。

解法一:递归需要一个base case,这个base case的获得可以通过逻辑运算符。因为 &&与|| 运算,当逻辑运算符左侧的运算能直接决定整个表达式的结果的时候,右侧的运算就不会执行。例如false&&true,左侧false,直接整个表达式为fasle;true||false,表达式为true。因此我们需要将递归式放进逻辑表达式中,以达到停止递归的目的。

解法二:用库函数Math.pow(n, 2),结合等差数列求和公式与位运算,即(Math.pow(n, 2)+n)»1

解法三:利用构造函数构建对象作为计数。我们可以定义两个静态变量N,sum,在每次构造对象的时候加1,sum += N。然后建立一个包含n个对象的数组,获取sum即可。

1
2
3
4
5
6
7
public class Solution {
    public int Sum_Solution(int n) {
        int sum = n;
        boolean x = ((n > 0) && (sum+=Sum_Solution(n - 1)) > 0);
        return sum;
    }
}