剑指offer(二)
概述
继续刷题。。。
第九题
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路
这道题的名字叫做变态跳台阶问题,那为什么叫变态我就不知道了,其实和第8题思路差不多。
- 假设第一次青蛙跳1级,那剩余n-1级,也就是还有f(n-1)种跳法
- 假设第一次青蛙跳2级,那剩余n-2级,也就是还有f(n-2)种跳法
- ...
- 假设第一次青蛙跳n级,那剩余n-n级
- 总结下来就是 f(n) = f(n-1) + f(n-2) + f(n-3) +...+ f(0)
- 由于f(0) = 1,f(1) = 1,f(2) = 2f(1),f(3) = 2f(2),所以f(n) = 2f(n-1) = 2(n-1)
代码实现
public class Solution { public int JumpFloorII(int target) { if(target <= 0){ return 0; } return (int)Math.pow(2,target-1); } }
第十题
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路
这道题叫做矩形覆盖问题,其实和第7题,第8题,第9题差不多,这道题也是使用斐波那契数列解决
- 由于直接考虑整个矩形覆盖无从下手,那就考虑刚开始的地方,如果第一个2*1的小矩形竖着放(也就是还是2*1),那大矩形剩余2*(n-1),那就还有f(n-1)种覆盖方法
- 如果第一个小矩形横着放(也就变成1*2),那这个小矩形下面的空间只能再横着放一个小矩形,那大矩形剩余2*(n-2),也就还有f(n-2)种覆盖方法
- 综上所述,f(n) = f(n-1) + f(n-2)
代码实现
public class Solution { public int RectCover(int target) { if(target <= 0){ return 0; } if(target == 1){ return 1; } if(target == 2){ return 2; } int a = 1; int b = 2; int c = 0; for(int i = 3;i <= target;i++){ c = a + b; a = b; b = c; } return c; } }
第十一题
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
解题思路
这个题如果不仔细思考很简单,如果仔细思考,就比较复杂了,比如题中说的是一个整数,那这个整数是int类型,还是long类型的,虽然牛客网上代码限定是int类型,因为int类型无论是32位编译器还是64位编译器都是占用4位,也就是32个字节,如果是这样,那就简单了,因为负数在计算机中本身就是使用补码存储的,所以可以使用如下方式解决。
代码
直接获取整数的二进制表示,统计一下1的个数,我并不喜欢这种解决方式(这种时间复杂度是O(n))
public class Solution { public int NumberOf1(int n) { int t=0; char[]ch=Integer.toBinaryString(n).toCharArray(); for(int i=0;i<ch.length;i++){ if(ch[i]=='1'){ t++; } } return t; } }
还有一种就是通过位运算来统计,时间复杂度为O(m),其中m为1的个数
public class Solution { public int NumberOf1(int n) { int count = 0; while(n!= 0){ count++; n = n & (n - 1); } return count; } }
由于我在第一次做这个题的时候,并不知道怎么获取计算机中整数二进制表示,而且没有想到使用位运算,想了一种非常复杂的方法,先贴在这里,方便以后看
import java.util.Stack; public class Solution { public int NumberOf1(int n) { if (n == 0){ return 0; } int bark = n; n = Math.abs(n); Stack<Integer> stack = new Stack(); while(n != 0){ int result = n % 2; if(result == 0){ stack.push(0); n = n/2; }else{ stack.push(1); n = (n - result)/2; } } int result = 0; int count = 0; if (bark > 0){ while(!stack.isEmpty()){ result = (int)stack.pop(); if(result == 1){ count++; } } }else{ int[] array = new int[32]; int length = stack.size(); for(int i=0;i<array.length;i++){ if(array.length - i == stack.size()){ result = (int)stack.pop(); if(result == 1){ array[i] = 0; }else{ array[i] = 1; } }else{ array[i]=1; } } for(int i=array.length-1;i>=0;i--){ if(array[i]==0){ array[i] = 1; break; }else{ array[i]=0; if(i-1<0){ return -1; }else{ if(array[i-1] == 0){ array[i-1] = 1; break; } } } } for(int i=0;i<array.length;i++){ if(array[i]==1){ count++; } } } return count; } }
第十二题
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
保证base和exponent不同时为0
解题思路
这道题,咋一看很简单,直接写个for循环,或者使用系统自带的函数都可以轻松解决,但是这个题并不是看你能不能把结果搞出来,而是看你怎么搞。
如果直接使用for循环,时间复杂度为O(n),比如计算38,需要8次计算,那有没有比这个次数更少的方法呢?答案是有的,就是快速求幂算法,下面简单介绍一下这个算法。
如果计算38,我们肯定要计算3*3,这是32,那其实38 = (34 * 34) = (32 * 32) * (32 * 32),所以最后我们只需要计算3次,第一次计算3*3=32 ,第二次计算32 * 32 = 34,第三次计算34 * 34 = 38。
好有了上面的基础,我们就可以正式来解决我们的问题,我们发现8的8位二进制表示为:0000 1000,我们发现其实二进制中1的位置右边3个0,正好是要计算的3次,(这里表述有问题,大家酬和着看,意思就是这个意思)
代码
直接计算结果的方法(简单又粗暴)
public class Solution { public double Power(double base, int exponent) { return Math.pow(base,exponent); } }
使用快速幂运算
def fast_power(self, base, exponent): if base == 0: return 0 if exponent == 0: return 1 e = abs(exponent) tmp = base res = 1 while(e > 0): #如果最后一位为1,那么给res乘上这一位的结果 if (e & 1 == 1): res =res * tmp e = e >> 1 tmp = tmp * tmp return res if exponent > 0 else 1/res
第十三题
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路
这个题我想的思路很简单,直接把奇数和偶数先找出来分别存放,之后再合并到一起。
代码
public class Solution { public void reOrderArray(int [] array) { int[] a = new int[array.length]; int[] b = new int[array.length]; int j = 0; int k = 0; for(int i = 0;i<array.length;i++){ if(array[i] % 2 == 1){ a[j] = array[i]; j++; }else{ b[k] = array[i]; k++; } } System.arraycopy(a,0,array,0,j); System.arraycopy(b,0,array,j,k); } }
第十四题
输入一个链表,输出该链表中倒数第k个结点。
解题思路
这个题按照常规的思考方式来,就是先遍历一遍,记录一下总数,之后再去遍历找到倒数第k个,这个时间复杂度是很高的,有一种更好的解决办法,使用快慢指针解决,具体就是第一个指针先走,当走到第K个节点的时候,第二个指针开始走,当第一个指针走到结尾的时候,第二个指针指向的就是倒数第k个节点
代码
public class Solution { public ListNode FindKthToTail(ListNode head,int k) { ListNode p = head; int i; for(i = 0;head != null;i++){ if(i > k-1){ p = p.next; } head = head.next; } if(i < k){ return null; } return p; } }
第十五题
输入一个链表,反转链表后,输出新链表的表头。
解题思路
这个题很简单,就是遍历的时候修改一下指针的方向就可以了,可能出问题的地方就是要搞清楚每个变量到底是指向哪个节点,不要两个变量指向同一个节点,一个变量把这个节点修改了,那另一个变量自然也就跟着变了。
代码
public class Solution { public ListNode ReverseList(ListNode head) { if(head == null){ return null; } ListNode temp = new ListNode(head.val); while(head.next != null){ ListNode temp1 = new ListNode(head.next.val); temp1.next = temp; temp = temp1; head = head.next; } return temp; } }
第十六题
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路
这个题和上一个题差不太多,就是遍历两个链表,然后逐个比较大小,最后构建一个新的链表
代码
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode Merge(ListNode list1,ListNode list2) { if(list1 == null){ return list2; } if(list2 == null){ return list1; } if(list1 == null && list2 == null){ return null; } ListNode next1; ListNode next2; ListNode next = null; if(list1.val < list2.val){ next = list1; list1 = list1.next; }else{ next = list2; list2 = list2.next; } ListNode temp = next; while(list1 != null && list2 != null){ if(list1.val >= list2.val){ next2 = list2.next; temp.next = list2; temp = temp.next; list2 = next2; }else{ next1 = list1.next; temp.next = list1; temp = temp.next; list1 = next1; } } if(list1 != null){ temp.next = list1; } if(list2 != null){ temp.next = list2; } return next; } }
写在最后的话
最近股市上涨,我也跟风买,谁知买在了高点,每天心心念念想着盯盘,为了百分之几的利润高兴或者难过,今天全部清仓,赔了10几个点,反而变得清净了,果然是人若没有执着,没有贪,没有嗔,没有痴,就会变得平静,就会有定。