转眼间就到了年末,前些天一直忙着论文投稿和开题报告。刷题都怠慢了,现在赶紧补上!
让我们用刷题来告别2019,用刷题来迎接2020!废话不多说,直接开始!
31.求出1-13的整数中1出现的次数,并算出100-1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
最简单最直观的做法:一个个去判断,然后外层循环到n就行了。但是这样的做法效率很低。
//运行时间:6ms 占用内存:500k
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n) {
int number=0;
if(n<1)
return 0;
for(int i=1;i<=n;++i) {
int temp=i;
while(temp) {
if(temp%10==1)
++number;
temp/=10;
}
}
return number;
}
};
还有这个方法,给跪了orz!
//运行时间:6ms 占用内存:476k
int ones = 0;
for (long long m = 1; m <= n; m *= 10) {
int a = n/m, b = n%m; //m表示当前分析的是哪一个数位
ones += (a + 8) / 10 * m + (a % 10 == 1) * (b + 1);
}
return ones;
主要思路:设定整数点(如1、10、100等等)作为位置点i(对应n的各位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析:
- 根据设定的整数位置,对n进行分割,分为两部分,高位n/i,低位n%i
- 当i表示百位,且百位对应的数>=2,如n=31456,i=100,则a=314,b=56,此时百位为1的次数有
- a/10+1=32(最高两位0~31),每一次都包含100个连续的点,即共有(a%10+1)*100个点的百位为1
- 当i表示百位,且百位对应的数为1,如n=31156,i=100,则a=311,b=56,此时百位对应的就是1,则共有a%10(最高两位0-30)次是包含100个连续点,当最高两位为31(即a=311),本次只对应局部点00~56,共b+1次,所有点加起来共有(a%10*100)+(b+1),这些点百位对应为1
- 当i表示百位,且百位对应的数为0,如n=31056,i=100,则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30)
- 综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有100个点,还有当百位为1(a%10==1),需要增加局部点b+1
- 之所以补8,是因为当百位为0,则a/10==(a+8)/10,当百位>=2,补8会产生进位位,效果等同于(a/10+1)
32.输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
**解题思路:先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。 排序规则如下:
- 若ab > ba 则 a > b,
- 若ab < ba 则 a < b,
- 若ab = ba 则 a = b;
**解释说明:**比如 “3” < "31"但是 “331” > “313”,所以要将二者拼接起来进行比较
对vector容器内的数据进行排序,按照 将a和b转为string后, 若 a+b<b+a a排在在前 的规则排序, 如 2 21 因为 212 < 221 所以 排序后为 21 2
//运行时间:4ms 占用内存:488k
class Solution {
public:
static bool cmp(int a,int b){
string A="";
string B="";
A+=to_string(a);//to_string() 可以将int 转化为string
A+=to_string(b);
B+=to_string(b);
B+=to_string(a);
return A<B;
}
string PrintMinNumber(vector<int> numbers) {
string answer="";
sort(numbers.begin(),numbers.end(),cmp);
for(int i=0;i<numbers.size();i++){
answer+=to_string(numbers[i]);
}
return answer;
}
};
33.把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方***得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:
(1)丑数数组: 1
乘以2的队列:2
乘以3的队列:3
乘以5的队列:5
选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(2)丑数数组:1,2
乘以2的队列:4
乘以3的队列:3,6
乘以5的队列:5,10
选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(3)丑数数组:1,2,3
乘以2的队列:4,6
乘以3的队列:6,9
乘以5的队列:5,10,15
选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(4)丑数数组:1,2,3,4
乘以2的队列:6,8
乘以3的队列:6,9,12
乘以5的队列:5,10,15,20
选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(5)丑数数组:1,2,3,4,5
乘以2的队列:6,8,10,
乘以3的队列:6,9,12,15
乘以5的队列:10,15,20,25
选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;
……………………
疑问:
1.为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
2.为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
实现思路:
我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;
(1)1
|2
|3
|5
目前指针指向0,0,0,队列头arr[0] * 2 = 2, arr[0] * 3 = 3, arr[0] * 5 = 5
(2)1 2
2 |4
|3 6
|5 10
目前指针指向1,0,0,队列头arr[1] * 2 = 4, arr[0] * 3 = 3, arr[0] * 5 = 5
(3)1 2 3
2| 4 6
3 |6 9
|5 10 15
目前指针指向1,1,0,队列头arr[1] * 2 = 4, arr[1] * 3 = 6, arr[0] * 5 = 5
………………
//运行时间:4ms 占用内存:468k
class Solution {
public:
int GetUglyNumber_Solution(int index) {
// 0-6的丑数分别为0-6
if(index < 7) return index;
//p2,p3,p5分别为三个队列的指针,newNum为从队列头选出来的最小数
int p2 = 0, p3 = 0, p5 = 0, newNum = 1;
vector<int> arr;
arr.push_back(newNum);
while(arr.size() < index) {
//选出三个队列头最小的数
newNum = min(arr[p2] * 2, min(arr[p3] * 3, arr[p5] * 5));
//这三个if有可能进入一个或者多个,进入多个是三个队列头最小的数有多个的情况
if(arr[p2] * 2 == newNum) p2++;
if(arr[p3] * 3 == newNum) p3++;
if(arr[p5] * 5 == newNum) p5++;
arr.push_back(newNum);
}
return newNum;
}
};
34.在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
先在hash表中统计各字母出现次数,第二次扫描直接访问hash表获得次数。
//运行时间:4ms 占用内存:476k
class Solution {
public:
int FirstNotRepeatingChar(string str) {
if (str.length() == 0)
return -1;
const int tableSize = 256;
unsigned int hashTable[tableSize]={0};
for(int i =0;i<str.length();++i)
hashTable[str[i]]++;
for(int i =0;i<str.length();++i) {
if( hashTable[str[i]] == 1)
return i;
}
return -1;
}
};
35.在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
这道题是真的挺难的啊!
思路一:
暴力解,两个循环,时间复杂度O(N2 ),但是会超出运行时间,导致失败。
思路二:
分治的思想,将数组不断一分为二,直到数组中只有两个元素,统计逆序对个数。然后对相邻的两个子数组进行合并,由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。在合并的时候也要计算组间的逆序对个数。
逆序对的总数 = 左边数组中的逆序对的数量 + 右边数组中逆序对的数量 + 左右结合成新的顺序数组时中出现的逆序对的数量
整个过程是一个归并排序的算法。
归并排序的性能不受输入数据的影响,时间复杂度始终都是O(nlogn)O(nlogn)。代价是需要额外的内存空间。
参考代码:
static const auto io_sync_off = []() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return nullptr;
}( );
class Solution {
public:
static constexpr int P = 1000000007;
vector<int>::iterator it;
int InversePairs(vector<int>& data) {
it = data.begin();
if (data.empty())return 0;
vector<int> dup(data);
return merge_sort(data.begin(), data.end(), dup.begin());
}
//template<class RanIt>
using RanIt = vector<int>::iterator;
int merge_sort(const RanIt& begin1, const RanIt& end1, const RanIt& begin2) {
int len = end1 - begin1;
if (len < 2)return 0;
int mid = ( len + 1 ) >> 1;
auto m1 = begin1 + mid, m2 = begin2 + mid;
auto i = m1, j = end1, k = begin2 + len;
int ans = ( merge_sort(begin2, m2, begin1) + merge_sort(m2, k, m1) ) % P;
for (--i, --j, --k; i >= begin1 && j >= m1; --k) {
if (*i > *j) {
*k = *i, --i;
( ans += j - m1 + 1 ) %= P;
} else *k = *j, --j;
}
if (i >= begin1)copy(begin1, i + 1, begin2);
else copy(m1, j + 1, begin2);
return ans;
}
};
36.输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode *p1=pHead1;
ListNode *p2=pHead2;
int len1=0,len2=0,diff=0;
while(p1!=NULL){
p1=p1->next;
len1++;
}
while(p2!=NULL){
p2=p2->next;
len2++;
}
if(len1>len2){
diff=len1-len2;
p1=pHead1;
p2=pHead2;
}
else{
diff=len2-len1;
p1=pHead2;
p2=pHead1;
}
for(int i=0;i<diff;i++){
p1=p1->next;
}
while(p1!=NULL && p2!=NULL){
if(p1==p2)
break;
p1=p1->next;
p2=p2->next;
}
return p1;
}
};
37.统计一个数字在排序数组中出现的次数。
看见有序,肯定就是二分查找了,算法比较简单,不多说,值得一提的是,循环和递归都要熟练的写出来才行。
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
int start,end,mid,count=0,i;
unsigned int len = data.size();
if(!len)
return 0;
start =0;
end = len-1;
mid = start;
while(start<end)
{
mid = (start+end)>>1;
if(k >data[mid])
start = mid+1;
if(k<data[mid])
end = mid-1;
if(k==data[mid])
break;
}
i=mid;
while( (i>=0) && (k==data[i]))
{
i--;
count++;
}
i=mid+1;
while((i<len)&& (k==data[i]) )
{
i++;
count++;
}
return count;
}
};
38.输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
递归的写法,代码会非常简洁优美:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot==nullptr)
return 0 ;
return max(1+TreeDepth(pRoot->left), 1+TreeDepth(pRoot->right));
}
};
39.输入一棵二叉树,判断该二叉树是否是平衡二叉树。
平衡二叉树定义:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。换句话说,平衡二叉树的左右子树也是平衡二叉树,那么所谓平衡就是左右子树的高度差不超过1。
思路:根据定义,我们只需要后序遍历此树,从树的叶子节点开始计算高度,只要有一个子树不满足定义就返回-1,如果满足继续向上计算高度。
class Solution {
public:
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot == nullptr)
return true;
int leftDepth = getDepth(pRoot -> left);
int rightDepth = getDepth(pRoot -> right);
if(leftDepth > rightDepth + 1 || leftDepth + 1 < rightDepth)
return false;
else
return IsBalanced_Solution(pRoot -> left) && IsBalanced_Solution(pRoot -> right);
}
int getDepth(TreeNode* pRoot){
if(pRoot == nullptr)
return 0;
int leftDepth = getDepth(pRoot -> left);
int rightDepth = getDepth(pRoot -> right);
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}
};
40.一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路:
- 记数组中存在的唯一的两个数字分别为a,b 首先以二进制的角度去看所有的数字,每一位均为0,1组成
- 对所有数字进行个位上的异或,成对相同的数字结果为0,每一位上都这样异或依次,所以最终每一位上存在1的则必然是因为a,b在这一位上不同
- 根据最终结果上存在‘1’的这一位,将原数组分为两组,一组‘1’,一组‘0‘, 两组数字再分别异或,最终两个结果就是a,b;
参考代码:
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int num=0;
for(int i=0;i < data.size();i++){
num^=data[i];//所有数异或,结果为不同的两个数字的异或
}
int count=0;//标志位,记录num中的第一个1出现的位置
for(;count < data.size();count++){
if((num&(1<<count))!=0){
break;
}
}
int num_1 = 0;
int num_2 = 0;
for(int i=0; i < data.size(); i++){
if((data[i]&(1<<count))==0){//标志位为0的为一组,异或后必得到一个数字(这里注意==的优先级高于&,需在前面加())
num_1^=data[i];
}else{
num_2^=data[i];//标志位为1的为一组
}
}
*num1 = num_1;
*num2 = num_2;
}
};