1、给出两个整数a和b, 求他们的和, 但不能使用 +
等数学运算符。
难点:使用逻辑求两数的和
分析:先不考虑进位的相加得到的结果a,再只考虑进位有哪些将其左移一位得到b,最后将a、b相加(即重载)。
以3 + 5为例 3的二进制为 1 1,5的二进制为 1 0 1
可以这样做:1先给这两个数加起来不考虑进位,这样得到的结果为 1 1 0,会发现与^(异或符号)得到的结果相同,于是先给两个数做^运算;
2、接下来考虑进位,两个二进制数相加会有这么几种情况 1 1,0 0, 1 0, 0 1除第一种情况外其他情况均不产生进位,而1 1两数相加进1,结果得0,可以这样做先将两个数做&运算,再将结果左移1位,这样就模拟了进位
3、将第1步得到的没进位的和 和第2步的进位相加便是结果,下面是代码
{
if (num2==0) retur num1; //注意:这里的if条件语句是判断如果没有进位后,那么num1即为最后的结果,因此返回num1即可。它是终止条件。
int num3=num1^num2;
int carry=(num1&num2)<<1;
return add(num3,carry);
因此最后方法为如下,被注释的三行为网上的答案。
class Solution {
public:
/**
* @param a: An integer
* @param b: An integer
* @return: The sum of a and b
*/
int aplusb(int a, int b) {
if (b==0) return a;
int c=a^b;
int carry=(a&b)<<1;
return aplusb(c,carry);
// int c=a&b;
// int d=a^b;
// return c==0?d:aplusb(c<<1,d);
}
};
2、尾部的零。
程序代码如下:
class Solution {
public:
/*
* @param n: A long integer
* @return: An integer, denote the number of trailing zeros in n!
*/
long long trailingZeros(long long n) {
long long result=0;
while(n/5>0)
{
result=result+n/5;
n/=5;
}
return result;
}
};
3、统计数字
class Solution {
public:
/*
* @param : An integer
* @param : An integer
* @return: An integer denote the count of digit k in 1..n
*/
int digitCounts(int k, int n) {
int a[n+1];int result=0; //使用数组存储0~n的每个数,方便枚举测试,result用于记录结果
for(int i=0;i<=n;i++) a[i]=i; //将0~n存入a[n+1]数组
if(k==0) result=1; //当k==0时,如果按照下面的算法,会漏下a[i]是0的情况,因此在这里先加一
for(int i=0;i<=n;i++) //1~n每个数都与k值比较
{
int number=a[i];
while(number!=0)
{
if ((number%10)==k) result++;
number/=10;
}
}
return result;
}
};
4、丑数Ⅱ
首先最容易想到的方法就是暴力破解,思路非常简单,首先除2,直到不能整除为止,然后除5到不能整除为止,然后除3直到不能整除为止。最终判断剩余的数字是否为1,如果是1则为丑数,否则不是丑数。
代码如下:
- class Solution {
- public:
- /*
- * @param n an integer
- * @return the nth prime number as description.
- */
- int nthUglyNumber(int n) {
- // write your code here
- int countN = 0;
- int m = 0;
- int lastNumber = 2;
- while(countN < n)
- {
- m++;
- int number = m;
- while(number % 2 == 0)
- number = number / 2;
- while(number % 3 == 0)
- number = number / 3;
- while(number % 5 == 0)
- number = number / 5;
- if(number == 1)
- {
- countN++;
- }
- }
- return m;
- }
- };
第二种方法:
直接寻找丑数,由丑数的定义可知,任何一个丑数都是2^i * 3^j * 5^m这种形式的,因此不断寻找丑数,将他们按从小到大的顺序进行排列,直到第n个即为结果。
首先定义一个数组存放丑数,认为1是丑数,则初始化数组num[0] = 1,然后从2,3,5这三个种子中挑选,选择num[0]*2,num[0]*3,num[0]*5中最小的数为新的丑数,显然应该选择2,即num[1] = 2,然后在从2,3,5中选择,这时应该是从num[1]*2,num[0]*3,num[0]*5中进行选择,显然选择3,即num[2] = 3,然后再从num[1]*2,num[1]*3,num[0]*5中选择最小的,选择2,即num[3] = 4,依次进行如下操作,得到最终的结果。
- class Solution {
- public:
- /*
- * @param n an integer
- * @return the nth prime number as description.
- */
- int nthUglyNumber(int n) {
- // write your code here
- int *ugly = new int[n];
- ugly[0] = 1;
- int num_2 = 0;
- int num_3 = 0;
- int num_5 = 0;
- for(int i = 1;i<n;i++)
- {
- ugly[i] = min(min(ugly[num_2]*2,ugly[num_3]*3),ugly[num_5]*5);
- if(ugly[i] / ugly[num_2] == 2)
- num_2 ++;
- if(ugly[i] / ugly[num_3] == 3)
- num_3 ++;
- if(ugly[i] / ugly[num_5] == 5)
- num_5 ++;
- }
- return ugly[n-1];
- }
- };
- if(ugly[i] / ugly[num_2] == 2)
- num_2 ++;
- if(ugly[i] / ugly[num_3] == 3)
- num_3 ++;
- if(ugly[i] / ugly[num_5] == 5)
- num_5 ++;
这几段代码的意思是找出到底是2,3,5中哪个种子计算出的ugly[i],当然,有可能有多个种子,比如ugly[num_2]*2 == ugly[num_3]*3时,需要把num_2++,并且要使num_3++。因此这里不能使用if-else,要全部使用if进行判断。
本题中主要记住的是方法二中,模拟2^i * 3^j * 5^m的程序
5、第K大元素查找
方法一:直接排序查找法(时间效率低,不符合O(n)的要求,因此到77%数据通过后,显示超时)
最简单的想法是直接进行排序,算法复杂度是O(N*logN)。这么做很明显比较低效率,因为不要求别的信息只要计算出第K大的元素。当然,如果在某种情况下需要频繁访问第K大的元素就可以先进行一次排序在直接得出结果。
第一种方式是这样,用选择排序,冒泡法,或者交换排序这类的排序,对前K个元素进行排序。这三种算法也许不是最快的排序算法。但是都有个性质:计算出最大(小)的元素的算法复杂度是O(N)。这个过程不能中断,要计算第三大的元素必须建立在已经算出第二大的元素的基础上(因为每次都是计算当前数组最大)。所以它的算法复杂度是O(N*K);程序如下:
int result=1;
int kthLargestElement(int n, vector<int> &nums) {
// write your code here
//空间复杂度O(1)表示该程序所占用的空间和所用数据量无关。
/*时间复杂度O(n),即当数组元素为n个时,算法要执行的步数为C*n,C为常数
但是类似于冒泡排序法,时间复杂度就位n^2了
*/
//方法1、枚举比较
/******************************
int result=0;
int m=nums.size();
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
if(j==i) continue;
if (nums[i]<nums[j]) result++;
}
if (result==n-1) {return nums[i];}
else result=0;
}
//错误原因:超时了,数据只通过了77%
************************************/
方法二:(快速排序过程中判断第k大元素在左侧还是右侧,从而每次处理的数都是减半)
第二种方法是用快速排序的思想。快速排序每次把一个元素交换到正确的位置,同时把左边的都方上大的,右边都放上小的。这个算法每一次选取一个枢纽元,排序之后,查看枢纽元的位置。如果它的位置大于K,就说明,要求出前面一个子序列的第K大的元素。反之,如果小于K,就说明要求出在后面一个序列的第K - 前一个序列的长度个元素。
如此,就把这个问题改变成了一个可以用快排思想解决的问题。对于快速排序,算法复杂度是O(N*logN)。而这个算法的算法复杂度是O(N)。为什么呢?
其实这个地方的算法复杂度分析很有意思。第一次交换,算法复杂度为O(N),接下来的过程和快速排序不同,快速排序是要继续处理两边的数据,再合并,合并操作的算法复杂度是O(1),于是总的算法复杂度是O(N*logN)(可以这么理解,每次交换用了N,一共logN次)。但是这里在确定枢纽元的相对位置(在K的左边或者右边)之后不用再对剩下的一半进行处理。也就是说第二次插入的算法复杂度不再是O(N)而是O(N/2),这不还是一样吗?其实不一样,因为接下来的过程是1+1/2+1/4+........ < 2,换句话说就是一共是O(2N)的算法复杂度也就是O(N)的算法复杂度。
程序如下所示:
class Solution {
public:
/*
* @param n: An integer
* @param nums: An array
* @return: the Kth largest element
*/
int result=1;
int kthLargestElement(int n, vector<int> &nums) {
int N=nums.size();
getnumber(n,0,N-1,nums);
return result;
}
void getnumber(int m,int s, int t, vector<int> &nums)
{
int i,j;
if(s<t)
{
i=s; j=t+1;
while(1)
{
do i++; while(nums[i]>=nums[s] && i!=t);
do j--; while(nums[j]<=nums[s] && j!=s);
if(i<j) swap(nums[i],nums[j]);
else break;
}
swap(nums[j],nums[s]);
if(j==m-1) {result=nums[j];return;}
else if(j>m-1) {getnumber(m,s,j-1,nums);}
else if(j<m-1) {getnumber(m,j+1,t,nums);}
}
else { result=nums[s];return;}
//之前一直错误,是因为没有加这句话,这句话是补充了查找元素是两端的
}
};