第五章 优化时间和空间效率
本章核心内容和思想,就是降低时间复杂度:
(1)改用更加高效的算法。如39、40、42题
(2)用空间换时间。用数组实现简单哈希表在O(1)时间内知道任意字符出现的次数;创建一个缓存保存中间的计算结果避免重复计算;用递归思路求解问题的时候,如果有重复的子问题,也可以通过保存求解子问题的结果来避免重复计算。相关例题如10、49、50题
(3)嵌入式开发,不宜过度牺牲空间换时间。
以下是具体内容:
面试题39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
限制:
1 <= 数组长度 <= 50000
class Solution {
public:
int majorityElement(vector<int>& nums) {
//方法1:排序后中间的元素一定是出现超过一半的数字
// sort(nums.begin(),nums.end());
// return nums[nums.size()/2];
//方法2:哈希表
unordered_map<int,int> mp;
for(auto it : nums){
mp[it]++;
if(mp[it] > nums.size()/2){
return it;
}
}
return 0;
//方法3:超过一半的数字比其他所有数字的总和次数多
// int n=1;
// int result = nums[0];
// for(int i=1;i<nums.size();i++){
// if(n==0){
// result = nums[i];
// n=1;
// }else if(result == nums[i]){
// n++;
// }else{
// n--;
// }
// }
// return result;
}
};
面试题40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
注释都在代码里了,每一行都充满了思考:
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> res;//用于保存最终要返回的结果
multiset<int,greater<int>> leastNumbers;//定义一个multiset(底层是红黑树),能在log(n)查找、删除、插入元素;元素按从大到小排列
leastNumbers.clear();//清空内容,便于后续使用,保证用的是一个空的容器
if(k<1 || arr.size()<k){//如果找前0个数据,毫无意义;如果待查数组长度比k小,根本达不到前k个数据,只能达到数组长度个数据,因此也毫无意义。这两种情况直接返回空数组。
return {};
}
vector<int>::const_iterator iter = arr.begin();//定义遍历用的迭代器
for(;iter!=arr.end();++iter){//遍历
if(leastNumbers.size()<k){//没装够k个的时候,请放心大胆使劲装,不用管数值大小
leastNumbers.insert(*iter);
}else{//装够了k个时,要进行下面的判断,决定是否要装入新的数据
auto iterGreatest = leastNumbers.begin();//仅仅是个取当前容器中最大值的迭代器
if(*iter < *iterGreatest){//如果要插入的数据 小于 此时容器中的最大值,那肯定要取而代之了,成为前k小的数插入红黑树容器multiset中
leastNumbers.erase(iterGreatest);//辞旧
leastNumbers.insert(*iter);//迎新
}
}
}
//这里用一个for循环,将multiset中的元素,搬运到要返回的res数组中
for(auto iter=leastNumbers.begin();iter!=leastNumbers.end();++iter){
res.push_back(*iter);
}
return res;
}
};
面试题41. 数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构:
void addNum(int num) - 从数据流中添加一个整数到数据结构中。
double findMedian() - 返回目前所有元素的中位数。
示例 1:
输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]
示例 2:
输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]
限制:
最多会对 addNum、findMedia进行 50000 次调用。
思路:
将左边部分构造成一个大顶堆,右边部分构造成一个小顶堆。
这样,来一个数据,只要总元素个数为偶数,都把数据放到小顶堆;只要元素个数为奇数,都把数据放到大顶堆。
为了解决元素总个数为偶数时,来的新元素num比小顶堆的最小元素还要小,这种情况下,这个元素num其实应该放到左边的大顶堆,然后从大顶堆中取出最大值放到小顶堆中;
同理,为了解决元素总个数为奇数时,来的新元素num比大顶堆的最大元素还要大的尴尬情况,先把这个num放入右边的小顶堆,然后从小顶堆取出最小元素放到大顶堆。
注意:小顶堆不“小”,小顶堆中的所有元素都应该大于大顶堆的所有元素。
class MedianFinder {
priority_queue<int> bigHeap; // 大顶堆,首元素是堆中最大元素
priority_queue<int, vector<int>, greater<int>> smallHeap; // 小顶堆,首元素最小
public:
// Adds a number into the data structure.
void addNum(int num)
{
if(((bigHeap.size()+smallHeap.size())&0x1) == 0){//总元素个数是偶数,插入最小堆
if(bigHeap.size()>0 && num<bigHeap.top()){//最大堆不为空&&num<最大堆堆首,说明num应该出现在左半边,自然应该先插入最大堆
bigHeap.push(num);//num先插入最大堆
num = bigHeap.top();//然后取了最大堆堆首赋给num,后续将num插入最小堆
bigHeap.pop();//弹出最大堆堆首
}
smallHeap.push(num);//如果最大堆为空或num>最大堆堆首
}else{//总元素个数是奇数,插入最大堆
if(smallHeap.size()>0 && num>smallHeap.top()){
smallHeap.push(num);
num = smallHeap.top();
smallHeap.pop();
}
bigHeap.push(num);
}
}
// Returns the median of current data stream
double findMedian()
{
int size = smallHeap.size()+bigHeap.size();
// if(size==0) throw exception("No number are availabel");
double median=0.0;
if((size&0x1)==1){//元素个数为奇数时,此时如果再来数据应加入最大堆,说明此时最小堆元素数量多于最大堆
median = smallHeap.top();//中位数就是最小堆的堆顶元素
}else{//元素个数为偶数时,两个堆元素数量相同,中位数为两堆堆顶元素平均值
median = (smallHeap.top()+bigHeap.top())*0.5;//此处绝对不能用>>1,否则得到的都是整数,而不是分数
}
return median;
}
};
面试题42. 连续子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
代码:
class Solution {
public:
bool g_InvalidInput = false;
int maxSubArray(vector<int>& nums) {
if(nums.size()<=0){
g_InvalidInput = true;
return 0;
}
int nCurSum = 0;//当前和
int nGreatestSum = 0x80000000;//当前的最大和
for(int i=0;i<nums.size();++i){//遍历累加子数组和
if(nCurSum<=0){//如果当前和<=0,说明前面加来加去白加了
nCurSum = nums[i];//清空前面的结果
}else{//如果当前和>0,说明前面加的结果还是很有效的,下面继续累加
nCurSum += nums[i];
}
if(nCurSum>nGreatestSum){//如果加上这个nums[i]后,当前的和超过了当前最大和
nGreatestSum = nCurSum;
}
}
return nGreatestSum;
}
};
面试题43. 1~n整数中1出现的次数
输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。
例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。
示例 1:
输入:n = 12
输出:5
示例 2:
输入:n = 13
输出:6
限制:
1 <= n < 2^31
思路:
统计某个位置上 1出现的次数。如34,1在十位上出现的次数是10次(10到19),1在个位上出现的次数是4次(1,11,21,31),因此34中1出现了14次。
对于整数n,将这个整数分为三部分:当前位数字cur,更高位数字high,更低位数字low,如:对于n=21034,当位数是十位时,cur=3,high=210,low=4。
我们从个位到最高位 依次计算每个位置出现1的次数:
在计算时,会出现三种情况:
1)当前位的数字等于0时,例如 n = 21034
,在百位上的数字cur = 0
,百位上是1的情况有:00100-00199,01100-01199,……,20100-20199。一共有21*100
种情况,即high*100
。
2)当前位的数字等于1时,例如n = 21034
,在千位上的数字cur = 1
,千位上是1的情况有:01000-01999,11000-11999,21000-21034。一共有2*1000+(34+1)
种情况,即high*1000+(low+1)
。
3)当前位的数字大于1时,例如n = 21034
,在十位上的数字cur = 3
,十位上是1的情况有:00010-00019,……,21010-21019。一共有(210+1)*10
种情况,即(high+1)*10
。
class Solution {
public:
int countDigitOne(int n) {
int count = 0;
long i = 1;//指向遍历的位数,如i=1即个位,i=10即十位,...,因为n可以有31位,所以10^31用long存储
while(n/i !=0){ //n/i 控制遍历的次数,将所有的位数都遍历完毕
long high = n/(10*i);//将当前位之前的所有高位都存在high中
long cur = (n/i)%10;//将当前位记录在cur中,即每次都需要统计当前位上1出现的次数
long low = n-(n/i)*i;//低位数字就是n减去当前数字在内的全部数
if(cur == 0){
count += high * i;
} else if(cur == 1){
count += high * i + low + 1;
} else {
count += high * i + i;
}
i = i * 10;//准备遍历下一位
}
return count;
}
};
面试题44. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
限制:
0 <= n < 2^31
class Solution {
public:
int findNthDigit(int n) {
if(n<=0){//如果是空数组,不鸟他
return -1;
}
int digits = 1;//digits负责保存1,2,3,4,... 用来表示 位数
while(true){
long numbers = countOfIntegers(digits);//该函数得到digits位的数字总共有多少个,输入2,返回两位数的个数90,输入3返回3位数的个数900
if(n < numbers * digits){//总共numbers个数字,其中每个数字有digits位,总欧诺个就是两者乘积个位数,看n有没有被含在内
return digitAtN(n,digits);//如果n就在digits位的numbers个数字拆开的numbers*digits个位数以内,找到它
}
n -= digits * numbers;//如果n就在digits位的numbers个数字拆开的numbers*digits个位数以外,去掉这些累赘
++digits;//增大位数,继续找
}
return -1;
}
long countOfIntegers(int digits){//该函数得到digits位的数字总共有多少个,输入2,返回两位数的个数90,输入3返回3位数的个数900
if(digits==1){
return 10;
}
long count = (int)pow(10,digits-1);
return 9*count;
}
int digitAtN(int n,int digits){//当单个数字有digits位的时候,第n位是哪个数字。如:对于所有的5位数,第32位是什么数字
int numbers = beginNumber(digits) + n / digits;//第n位就位于数字numbers中
int nFromRight = digits - n % digits;//有一说一,这里我还不会,实在难题体会为何这里要来这么一招
for(int i=1;i<nFromRight;++i){//万分小心,如履薄冰
numbers /=10;
}
return numbers%10;
}
int beginNumber(int digits){//digits位的数字,最开始的那个值。3-->1000;2-->100
if(digits==1){
return 0;
}
return (int) pow(10,digits-1);
}
};
面试题45. 把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:
输入: [3,30,34,5,9]
输出: "3033459"
提示:
0 < nums.length <= 100
说明:
输出结果可能非常大,所以你需要返回一个字符串而不是整数
拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0
class Solution {
public:
string minNumber(vector<int>& nums) {
if(nums.size()<=0) return "";//不鸟空数组
vector<string> vs;//定义一个字符串数组
for(auto iter = nums.begin();iter!=nums.end();++iter){//如数拷贝成字符串
vs.push_back(to_string(*iter));
}
//玄妙又玄妙,深远又深远,这里运用了lamda表达式,让字符数组中的数字字符串顺序变成,前串+后串 < 后串+前串
sort(vs.begin(),vs.end(),
[](const string& s1,const string& s2){return s1+s2<s2+s1;});
string temp="";//把字符数组中的字符串们如数拷贝到一个字符串中
for(auto s:vs){
temp += s;
}
return temp;
}
};
面试题46. 把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
示例 1:
输入: 12258
输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
提示:
0 <= num < 2^31
无比详细的解释,都在注释里了:
class Solution {
public:
int translateNum(int num) {
if(num<0){//如果num<0,直接返回0,意思是没有任何办法把一个负数翻译成字符串
return 0;
}
string number = to_string(num);//把int转换成字符串number
int length = number.length();//字符串中的字符总个数
vector<int> counts(length,0);//新建一个长度为字符总个数的数组,初始化为0。由于每个元素与number中的每个字符一一对应,可以用来记录遍历到每一个字符时对应的可能的翻译种类
int count=0;//定义一个局部变量count用来记录从后往前扫描到当前字符时对应的可能的翻译种类
for(int i=length-1;i>=0;--i){//从后往前扫描
count = 0;//初始化为0
if(i<length-1){//当不是最后一个元素的时候(大多数情况,因此放前)
count = counts[i+1];//count初始化为和它后面的字符的统计总数一样;因为,偌大一个字符串,当当前字符定死了以后,只需要看后面所有字符所有的翻译种数,就表示当前字符单独翻译情况下的翻译种数
}else{//当是最后一个元素的时候(仅一种情况,因此放后)
count = 1;//初始化为1
}
if(i<length-1){//当不是最后一个元素的时候,才能观察当前字符+后面一个字符。
int digit1 = number[i]-'0';//取得当前字符对应的数字
int digit2 = number[i+1]-'0';//取得后一个字符对应的数字
int convert = digit1*10+digit2;//拿到当前字符和后面字符组合成的数字(至少是个有效的两位数10-25)
if(convert>=10 && convert<=25){//convert在10到25的时候,说明这两个数字字符可能组成一个合法的两位数数字,这个两位数数字仅代表一个字母
if(i<length-2){//如果当前字符距离尾部至少还有两个字符的距离,也就是说至少:当前字符-后字符-后字符-空尾。其他情况下:当前字符-后字符-后字符-后字符-...-空尾
count += counts[i+2];//此时让当前字符可能出现的翻译种数 累加上 从后两个字符开始到末尾可能的翻译种数;因为当前字符和他的后一字符已经组合成了一个字符,此时,具体有多少种可能性,只能看剩下的后面那些字符的翻译种数
}else{//如果当前字符距离尾部不到两个字符的距离,那不可能出现counts[i+2],因为i+2下标越界了,可能的情况是:当前字符-后字符-空尾、当前字符-空尾
count += 1;//count递增1
}
}
}
counts[i] = count;//不管上头是不是最后一个元素,你们把数据处理好,我这里直接存入数组
}
count= counts[0];//前面的for循环保证counts数组中的数字被填满,这里的这句就是从counts数组中取出最开头的那个数字,表示从第0位开始到字符串末尾的排列方法总数
return count;
}
};
面试题47. 礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
提示:
0 < grid.length <= 200
0 < grid[0].length <= 200
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
int rows = grid.size();
int cols = grid[0].size();
vector<vector<int>> res(rows,vector<int>(cols,-1));//定义一个二维数组res,用于保存与grid对应的位置,走到那个位置,对应的礼物最大价值
res[0][0] = grid[0][0];//初始值先拿过来
for(int i=1;i<rows;++i){//先初始化第一列
res[i][0] = res[i-1][0]+grid[i][0]; //第一列res的值,只能从上面的位置过来,而无法从左边的位置过来,只需要将当前的grid值与上一个res值加起来
}
for(int j=1;j<cols;++j){//再初始化第一行
res[0][j] = res[0][j-1] + grid[0][j];//第一行res的值,只能从左边的位置过来,而无法从上面的位置过来,只需要将当前的grid值与左边的res值加起来
}
for(int i=1;i<rows;++i){//这样就能放心大胆开始计算其他的res二维数组中的值了
for(int j=1;j<cols;++j){
res[i][j] = max(res[i-1][j],res[i][j-1])+grid[i][j];//当前节点所取得的gift如何能达到最大,那肯定是当前的格子中的gift最大,并且从一个gift和最大的格子走过来,那样才会最大。既然当前格子的gift无法选择,所以只能尽量从两个可能过来的方向的格子中选一个累加gift和最大的格子,作为前一个跳板
}
}
return res[rows-1][cols-1];//右下角的就是最大的
}
};
面试题48. 最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
提示:
s.length <= 40000
和书上的原题不同,这道题目比书上的原题更苛刻,书上字符串只包含a-z字母,这道题可能出现其他的字符。因此不可以像书上那样草草定义一个长度为26的position数组了事,这里使用map记录当前字符上次出现的位置i,所有思考全在注释里了:
class Solution {
public:
int lengthOfLongestSubstring(string s){
if (s.empty()){//不鸟空字符串
return 0;
}
map<char, int> mp; // 用来保存字符char最新一次出现的位置int
int maxLength = 0; // 最长不含重复字符的字符串长度
int curLength = 0; // 当前不含重复字符的字符串长度
for(int i=0; i < s.size(); i++){//挨个遍历每个字符
// 第 i 个字符之前没有出现过, f(i) = f(i-1)+1
if(mp.find(s[i]) == mp.end()){
curLength++;
}
// 第 i 个字符之前出现过,d表示第i个字符和它上次出现在字符串中的位置的距离i-map[s[i]]
else if(i-mp[s[i]] <= curLength){ // d <= f(i-1)时,说明当前字符上一次出现的位置其实是位于最新连续字符串当中的,说明必然会造成连续字符串的破裂,从哪破裂呢,肯定是从当前字符上次出现的位置,后面的一个位置开始,直到并包括当前字符结束,所以长度为:f(i)=d
maxLength = maxLength > curLength ? maxLength:curLength;//看是否需要更新最大长度,因为马上要截断了,会导致curLength减小,如果此时的长度比此时的最大长度还要大,那得需要更新最大长度;如果此时长度并没大过最大长度,则不用更新
curLength = i - mp[s[i]];//当前长度肯定需要更新了,更新成d
}
else{ // d > f(i-1),此时位于i位置的当前字符并没有出现在已经形成的连续字符串内,因此丝毫不会影响已经形成的连续字符串,因此加上这个位于i位置的当前字符,只能说明连续字符串长度可以继续递增一个:f(i) = f(i-1)+1
curLength++;
}
mp[s[i]] = i;//最后更新当前字符s[i]最新出现的位置,就是i
maxLength = maxLength > curLength ? maxLength:curLength;//看是否需要更新最大值
}
return maxLength;
}
};
面试题49. 丑数
我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
此题不太懂,后续再看:
class Solution {
public:
int nthUglyNumber(int n) {
if(n<=0)return 0;
vector<int> UglyNum(n,0);
UglyNum[0] = 1;
int nextUglyIndex = 1;
int p2=0,p3=0,p5=0;
while(nextUglyIndex < n){
int min = UglyNum[p2]*2 < UglyNum[p3]*3 ? UglyNum[p2]*2 : UglyNum[p3]*3;
min = UglyNum[p5]*5 < min ? UglyNum[p5]*5 : min;
UglyNum[nextUglyIndex] = min;
while(UglyNum[p2]*2<=min){
++p2;
}
while(UglyNum[p3]*3<=min){
++p3;
}
while(UglyNum[p5]*5<=min){
++p5;
}
++nextUglyIndex;
}
return UglyNum[nextUglyIndex - 1];
}
};
面试题50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
限制:
0 <= s 的长度 <= 50000
class Solution {
public:
char firstUniqChar(string s) {
vector<unsigned int> hashTable(256,0);//创建一个数组,作为哈希表,索引范围是0-255共256个字符,因为ASCII码的个数就是256个,每个字符对应的ASCII码就是从0到255的数字,根据这个字符对应的ASCII码将字符存入数组的对应位置
auto iter = s.begin();//从字符串s的开头遍历到结尾
while(iter!=s.end()){
hashTable[*(iter++)]++;//这一遍遍历是为了,将每个字符对应的位置计数值加1
}
iter = s.begin();//从字符串s的开头遍历到结尾
while(iter!=s.end()){
if(hashTable[*iter]==1){//这一遍遍历是为了,找到最靠近字符串s开头的计数值为1的那个字符
return (char)(*iter);
}
iter++;
}
return ' ';//若没有次数为1,则输出空格' ' ,这一点很重要
}
};
面试题51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
知识点1:归并排序中略动的手脚
这题可以用归并排序来做,只在归并排序基础上加一句代码。针对添加的这句神来之笔,我的理解是这样的:
左侧数组是[left,mid]
,右侧数组是[mid+1,right]
,如果发现左侧下标为i的元素比右侧下标为j
的元素大,说明,[i,mid]
的每个数据都比右侧下标为j
的元素大,[i,mid]
中共有mid-i+1
个元素,所以,此处会形成mid-i+1
个逆序对。
为啥是mid-i
后还要+1
呢,是因为,要把当前的下标为i
的这个元素算上,因为它本身就比下标为j
的这个元素要大,两者也能构成逆序对
知识点2:C++ STL中copy函数的使用
如果要把一个序列(sequence)拷贝到一个容器(container)中去,通常用std::copy算法,代码如下:
std::copy(start, end, std::back_inserter(container));
即:
std::copy(start, end, container.begin());
这里,start和end是输入序列的迭代器(iterator),container是一个容器,该容器的接口包含函数push_back,复制的元素都被追加到container的末尾了。
完成代码如下
class Solution {
public:
int global_count = 0; //全局变量
int reversePairs(vector<int>& nums) {
vector<int> copyarr(nums.size(), 0);
merge_sort(nums, copyarr, 0, nums.size()-1);
return global_count;
}
void merge_sort(vector<int>& nums, vector<int>& copyarr, int left, int right) {
if (left >= right) return;
int mid = (left+right) / 2;
merge_sort(nums, copyarr, left, mid);
merge_sort(nums, copyarr, mid+1, right);
int i = left, j = mid+1, k = left;
while (i <= mid && j <= right) {
if (nums[j] < nums[i]) {
copyarr[k++] = nums[j++];
global_count += (mid-i+1);// 关键点,也是归并排序添加的唯一一行代码。
} else {
copyarr[k++] = nums[i++];
}
}
if (i <= mid) copy(nums.begin()+i, nums.begin()+mid+1, copyarr.begin()+k);//把nums数组的[i,mid+1)拷贝到从k开始的位置
if (j <= right) copy(nums.begin()+j, nums.begin()+right+1, copyarr.begin()+k);//把nums数组的[j,right+1)拷贝到从k开始的位置
copy(copyarr.begin()+left, copyarr.begin()+right+1, nums.begin()+left);//最后,把copyarr所有元素拷贝到nums从left开始的位置
}
};
面试题52. 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA==NULL || headB == NULL)return NULL;
unsigned int lenA = GetListLength(headA);
unsigned int lenB = GetListLength(headB);
//默认情况是A比B长,或者A与B一样长
ListNode * longList = headA;
ListNode * shortList = headB;
int gap = lenA-lenB;
//如果A比B短,那么调整一下
if(lenA<lenB){
longList = headB;
shortList = headA;
gap = lenB-lenA;
}
//先让长链表走上gap。 如果gap是零,说明A与B等长,那就不执行这个for循环
for(int i=0;i<gap;++i){
longList = longList->next;
}
//让两个链表一起往下走。 如果gap是零,也不要紧,俩人一同起步边走边比较,如果走到两个链表的最后还不同,那么此时的longList和shortList就是NULL,随便返回一个即可
while(longList!=NULL && shortList!=NULL && longList!=shortList){
longList = longList->next;
shortList = shortList->next;
}
ListNode * pFirstCommonNode = longList;//or shortList;得到第一个公共节点
return pFirstCommonNode;
}
int GetListLength(ListNode* pHead){
int len = 0;
ListNode * pNode = pHead;
while(pNode!=NULL){
++len;
pNode = pNode->next;
}
return len;
}
};