剑指offer第六章
剑指offer第六章
1.数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在数组中出现了4次,所以输出4
分析:思路1:直官思路:直接顺序扫描,O(n)
1 class Solution { 2 public: 3 int GetNumberOfK(vector<int> data ,int k) 4 { 5 int count=0; 6 for(int i=0;i<data.size();i++) 7 { 8 if(data[i]==k) 9 count++; 10 } 11 return count; 12 13 } 14 };
思路2:因为是排序数组,所以想到二分查找,关键在于如何用二分查找找到第一个和最后一个k
找第一个k:先拿数组中间的数和k做比较,中间数字比k大,那么k只能出现在前半段,中间比k小,那么k只能出现在后半段,如果相等,先判断是不是第一个k如果中间数字的前一个不是k,则找到第一个k。如果前一个也是k,也就是说第一个k肯定在数组前半段,下一轮继续在前半段查找。
找最后一个k:同上
1 class Solution { 2 /*二分查找 找到第一个K 和 最后一个K 二者位置相减*/ 3 public: 4 int GetNumberOfK(vector<int> data ,int k) { 5 if(data.empty()) 6 return 0; 7 int number = 0; 8 int first = GetFirstIndex(data,k,0,data.size()-1); 9 int last = GetLastIndex(data,k,0,data.size()-1); 10 if(first>-1 && last>-1) 11 number = last - first +1; 12 return number; 13 14 } 15 int GetFirstIndex(vector<int> &data,int k,int start,int end){ 16 if(start > end) 17 return -1; 18 int mid = start+(end-start)/2; 19 if(data[mid] == k){ 20 if((mid == start) || (data[mid-1] != k)) 21 return mid; 22 else 23 end = mid-1; 24 } 25 else{ 26 if(data[mid] > k) 27 end = mid - 1; 28 else 29 start = mid + 1; 30 } 31 return GetFirstIndex(data,k,start,end); 32 } 33 int GetLastIndex(vector<int> &data,int k,int start,int end){ 34 if(start > end) 35 return -1; 36 int mid = start+(end-start)/2; 37 if(data[mid]==k){ 38 if((mid == end) || (data[mid+1] != k)) 39 return mid; 40 else 41 start = mid +1; 42 } 43 else{ 44 if(data[mid]>k) 45 end = mid-1; 46 else 47 start = mid+1; 48 } 49 return GetLastIndex(data,k,start,end); 50 } 51 };
2.二叉树的深度
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
分析:递归
1 /* 2 struct TreeNode { 3 int val; 4 struct TreeNode *left; 5 struct TreeNode *right; 6 TreeNode(int x) : 7 val(x), left(NULL), right(NULL) { 8 } 9 };*/ 10 class Solution { 11 public: 12 int TreeDepth(TreeNode* pRoot) 13 { 14 if(pRoot==NULL) 15 return 0; 16 int nLeft=TreeDepth(pRoot->left); 17 int nRight=TreeDepth(pRoot->right); 18 int depth=0; 19 if(nLeft>nRight) 20 depth=nLeft+1; 21 else 22 depth=nRight+1; 23 return depth; 24 } 25 };
3.平衡二叉树
输入一颗二叉树,判断该树是不是平衡二叉树。
如果某二叉树中任意结点的左右子树的深度相差不超过1,那么他就是一颗平衡二叉树
分析:容易想到的思路:在遍历树的每个结点的时候,调用函数TreeDepth得到他的左右子树的深度,如果每个结点的左右子树的深度相差不超过1,则就是平衡二叉树
1 class Solution { 2 public: 3 bool IsBalanced_Solution(TreeNode* pRoot) 4 { 5 if(pRoot==NULL) 6 return true; 7 int left=TreeDepth(pRoot->left); 8 int right=TreeDepth(pRoot->right); 9 int diff=left-right; 10 if(diff>1||diff<-1) 11 return false; 12 return IsBalanced_Solution(pRoot->left)&&IsBalanced_Solution(pRoot->right); 13 } 14 int TreeDepth(TreeNode* pRoot) 15 { 16 if(pRoot==NULL) 17 return 0; 18 int nLeft=TreeDepth(pRoot->left); 19 int nRight=TreeDepth(pRoot->right); 20 int depth=0; 21 if(nLeft>nRight) 22 depth=nLeft+1; 23 else 24 depth=nRight+1; 25 return depth; 26 } 27 };
4.数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都只出现了两次,找出这两个数字
要求:时间复杂度是O(n),空间复杂度O(1)
分析:首先:位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
两个相同数字异或=0,一个数和0异或还是它本身。
当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。
依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,记为第n位,接着把原数组分成两组,分组标准是第n位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
1 class Solution { 2 public: 3 void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) 4 { 5 int length=data.size(); 6 if(length<2) 7 return; 8 if(length==2) 9 { 10 num1[0] = data[0]; 11 num2[0] = data[1]; 12 return; 13 } 14 int bitResult = 0; 15 for(int i = 0; i < length; ++i) 16 { 17 bitResult ^= data[i]; 18 } 19 int index = findFirst1(bitResult); 20 *num1=*num2=0; 21 for(int i = 0; i < length; ++i) 22 { 23 if(isBit1(data[i], index)) 24 { 25 *num1 ^= data[i]; 26 } 27 else 28 { 29 *num2 ^= data[i]; 30 } 31 } 32 } 33 int findFirst1(int num) 34 { 35 int index=0; 36 while(((num&1)==0)&&(index<8*sizeof(int))) 37 { 38 num=num>>1; 39 ++index; 40 } 41 return index; 42 } 43 bool isBit1(int num,int index) 44 { 45 num=num>>index; 46 return (num&1); 47 } 48 };
5.和为s的两个数字
输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
分析:先在数组中选择两个数字,如果他们的和等于S,则找到;如果小于S,选择较小的数字后面的数字;如果大于S,选择较大数字前面的数字。因为数组已经是有序的。
1 class Solution { 2 public: 3 vector<int> FindNumbersWithSum(vector<int> array,int sum) 4 { 5 int length=array.size(); 6 vector<int> res; 7 int ahead=length-1; 8 int behind=0; 9 while(ahead>behind) 10 { 11 long long curSum=array[ahead]+array[behind]; 12 if(curSum==sum) 13 { 14 res.push_back(array[behind]); 15 res.push_back(array[ahead]); 16 break; 17 } 18 else if(curSum>sum) 19 { 20 ahead--; 21 } 22 else 23 { 24 behind++; 25 } 26 } 27 return res; 28 } 29 };
6.和为s的连续正数序列
输入一个正数S,打印出所有和为S的连续正数序列(至少含有两个数)
分析:首先考虑用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列和大于S,则去掉序列中较小的值,也就是增加small的值;如果小于S,增大big,让序列包含更多的数字,一直增加到small为(1+S)/2为止
1 class Solution { 2 public: 3 vector<vector<int> > FindContinuousSequence(int sum) 4 { 5 vector<vector<int> > res; 6 if(sum<3) 7 return res; 8 int small=1; 9 int big=2; 10 int middle=(1+sum)/2; 11 int curSum=small+big; 12 while(small<middle&&big<sum) 13 { 14 while(curSum > sum) 15 { 16 curSum -= small; 17 small++; 18 } 19 if(curSum==sum) 20 { 21 InsertRes(small,big,res); 22 } 23 big++; 24 curSum+=big; 25 } 26 return res; 27 } 28 void InsertRes(int small,int big,vector<vector<int> > &res) 29 { 30 vector<int> temp; 31 for(int i = small;i<=big;i++) 32 temp.push_back(i); 33 res.push_back(temp); 34 } 35 };
7.反转单词序列
输入一个英文句子,反转句子中单词的顺序,但单词内字符的顺序不变,标点符号和普通字母一样处理
分析:第一步翻转句子中所有字符;第二步再翻转每个单词中字符的顺序
1 class Solution { 2 public: 3 string ReverseSentence(string str) 4 { 5 ReverseWord(str,0,str.size()-1);//先整体翻转 6 int start=0,end=0; 7 int i=0; 8 int length=str.size(); 9 while(i<length) 10 { 11 while(i<length&&str[i]==' ')//空格跳过 12 { 13 i++; 14 } 15 start=end=i; 16 while(i<length&&str[i]!=' ') 17 { 18 i++; 19 end++; 20 } 21 ReverseWord(str,start,end-1); 22 } 23 return str; 24 } 25 void ReverseWord(string &str, int start, int end) 26 { 27 while(start < end) 28 { 29 swap(str[start++], str[end--]); 30 } 31 } 32 };
8.左旋转字符串
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它
分析:三次翻转,先翻转前n个部分,再翻转后一部分,再翻转整体
1 class Solution { 2 public: 3 string LeftRotateString(string str, int n) 4 { 5 int length = str.size(); 6 if(length<=0) 7 return str; 8 if(length>0&& n>0&&n<length) 9 { 10 int firstStart = 0; 11 int firstEnd = n-1; 12 int secondStart = n; 13 int secondEnd = length-1; 14 Reverse(str,firstStart,firstEnd); 15 Reverse(str,secondStart,secondEnd); 16 Reverse(str,firstStart,secondEnd); 17 } 18 return str; 19 } 20 void Reverse(string &str, int start, int end) 21 { 22 while(start < end) 23 { 24 swap(str[start++], str[end--]); 25 } 26 } 27 };
9.扑克牌的顺子
从扑克牌中随机抽取5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以为任意数字。
分析:首先把数组排序,在统计数组中0的个数,最后统计排序之后的数组中相邻数字之间的空缺总数。如果空缺总数小于或者等于0的个数,那么这个数组就是连续的;反之则不连续
注意:如果非零数字重复出现,则不连续
1 class Solution { 2 public: 3 bool IsContinuous( vector<int> numbers ) { 4 if(numbers.size()!=5) 5 return false; 6 sort(numbers.begin(),numbers.end()); 7 int i=0; 8 while(numbers[i]==0) 9 i++; 10 if(numbers[4]-numbers[i]>4) 11 return false; 12 for(int j=i;j<4;j++){ 13 if(numbers[j]==numbers[j+1]) 14 return false; 15 } 16 return true; 17 } 18 };
10.圆圈中最后剩下的数字
0,1,...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求这个圆圈里剩下的最后一个数字。
分析:两种解法:
解法一:经典解法,用环形链表模拟圆圈
1 class Solution { 2 public: 3 int LastRemaining_Solution(int n, int m) 4 { 5 if(n<1||m<1) 6 return -1; 7 int i=0; 8 list<int> numbers; 9 for(i=0;i<n;++i) 10 numbers.push_back(i); 11 list<int>::iterator current=numbers.begin();//开始点 12 while(numbers.size()>1) 13 { 14 for(int i=1;i<m;++i) 15 { 16 current++;//指针后移 17 if(current==numbers.end()) 18 current=numbers.begin(); 19 } 20 list<int>::iterator next=++current; 21 if(next==numbers.end()) 22 next=numbers.begin(); 23 --current; 24 numbers.erase(current); 25 current=next; 26 } 27 return *(current); 28 } 29 };
解法二:创新解法:找到数学规律
递推公式 f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
1 class Solution { 2 public: 3 int LastRemaining_Solution(unsigned int n, unsigned int m) 4 { 5 if(n==0) 6 return -1; 7 if(n==1) 8 return 0; 9 else 10 return (LastRemaining_Solution(n-1,m)+m)%n; 11 } 12 };
11.求1+2+3+...+n
求1+2+3+...+n要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句
分析:解题思路:
1.需利用逻辑与的短路特性实现递归终止。
2.当n==0时,sum&&((sum+=Sum_Solution(n-1)))只执行前面的判断,为false,然后直接返回0;
3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。
1 class Solution { 2 public: 3 int Sum_Solution(int n) 4 { 5 int sum = n; 6 sum&&((sum+=Sum_Solution(n-1))); 7 return sum; 8 } 9 };
12.不用加减乘除做加法
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
分析:用位运算代替二进制加法
第一步不考虑进位对每一位相加==》采用异或
接着考虑第二步进位,可以想象为是两个数先做位与运算,然后再向左移动一位
第三步把前两个步骤结果相加。第三步相加的过程依然是重复前两步,直到不产生进位为止
1 class Solution { 2 public: 3 int Add(int num1, int num2) 4 { 5 int sum,carry; 6 do{ 7 sum=num1^num2; 8 carry=(num1&num2)<<1; 9 num1=sum; 10 num2=carry; 11 }while(num2!=0); 12 return num1; 13 } 14 };