训练题:逆序对

题目:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

方法一:归并排序

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

归并排序刚好是将左半有序部分和右半有序部分进行合并。在这个时候我们可以统计逆序对。

  1. int reversePairs(vector<int>& nums) {
  2. if(nums.size() < 2) return 0;
  3. const auto size = nums.size();
  4. vector<int> tmpNums(size);
  5. return mergeSort(nums, tmpNums, 0, size - 1);
  6. }
  7. int mergeSort(vector<int>& nums, vector<int>& tmpNums, int start, int end){
  8. if(end <= start) return 0;
  9. int mid = start + ((end - start) >> 1);
  10. int left = start;
  11. int right = mid + 1;
  12. int k = left;
  13. int count = mergeSort(nums, tmpNums, start, mid) + mergeSort(nums, tmpNums, mid + 1, end);
  14. for(; left <= mid && right <= end;)
  15. {
  16. if(nums[left] <= nums[right]) tmpNums[k++] = nums[left++];
  17. else{ // left > right
  18. tmpNums[k++] = nums[right++];
  19. // 此时比num[right]大的数是num[left] ... num[mid]这几个数。
  20. count += (mid - left + 1);
  21. }
  22. }
  23. for(; left <= mid; ++left, ++k) tmpNums[k] = nums[left];
  24. for(; right <= end; ++right, ++k) tmpNums[k] = nums[right];
  25. copy( tmpNums.begin() + start, tmpNums.begin() + end + 1, nums.begin() + start );
  26. return count;
  27. }

方法二:树状数组(待完成)

训练题:机器人运动范围

题目:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/

方法一:深度优先遍历(递归)

  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)

算法训练_数组 - 图1

  1. int count;
  2. int tmp;
  3. int movingCount(int m, int n, int k) {
  4. if(m < 1 || n < 1 || m > 100 || n > 100 || k < 0 || k > 20) return -1;
  5. // dataMap用于记录遍历过的地方。
  6. vector<vector<int>> dataMap(m, vector<int>(n, 0));
  7. traverse(dataMap, m, n, k, 0, 0);
  8. return count;
  9. }
  10. void traverse(int dataMap[][100], const int& m, const int& n, const int& k, int x, int y){
  11. if( x == m || y == n || dataMap[x][y]) return;
  12. dataMap[x][y] = 1;
  13. tmp = x % 10 + x / 10 + y % 10 + y / 10;
  14. if(tmp > k) return;
  15. ++count;
  16. // 优化:从上图我们看出规律:从左上往右下遍历,必然能全部遍历到。
  17. // 往左(x-1)和往上(y-1)的遍历可以省去。
  18. traverse(dataMap, m, n, k, x + 1, y);
  19. traverse(dataMap, m, n, k, x, y + 1);
  20. // traverse(dataMap, m, n, k, x - 1, y);
  21. // traverse(dataMap, m, n, k, x, y - 1);
  22. }

方法二:广度优先遍历(队列)

  1. int movingCount(int m, int n, int k) {
  2. if(m < 1 || n < 1 || m > 100 || n > 100 || k < 0 || k > 20) return -1;
  3. int count = 0; // 可到达的格子数
  4. vector<vector<int>> dataMap(m, vector<int>(n, 0)); // 访问记录,避免重复
  5. queue<pair<char, char>> q; // 广度优先
  6. q.push({0, 0});
  7. for(; !q.empty(); q.pop()){
  8. auto pos = q.front();
  9. // 无效坐标:坐标超范围、已经访问过、坐标值数位之和大于k的坐标。
  10. if(pos.first == m || pos.second == n || dataMap[pos.first][pos.second]) continue;
  11. if(pos.first % 10 + pos.first / 10 + pos.second % 10 + pos.second / 10 > k) continue;
  12. ++count;
  13. dataMap[pos.first][pos.second] = 1;
  14. q.push({pos.first + 1, pos.second});
  15. q.push({pos.first, pos.second + 1});
  16. }
  17. return count;
  18. }

方法三:递推法

  1. int movingCount(int m, int n, int k) {
  2. if(m < 1 || n < 1 || m > 100 || n > 100 || k < 0 || k > 20) return -1;
  3. int count = 0;
  4. // canVisited[i][j] = canVisited[i-1][j] | canVisited[i][j-1]
  5. // 即只能从左边或者上边的元素过来。
  6. vector<vector<char>> canVisited(m, vector<char>(n, 0));
  7. canVisited[0][0] = 1;
  8. for(char row = 0; row < m; ++row){
  9. for(char col = 0; col < n; ++col){
  10. if(row%10 + row/10 + col%10 + col/10 > k) continue;
  11. if(row - 1 >= 0) canVisited[row][col] |= canVisited[row-1][col];
  12. if(col - 1 >= 0) canVisited[row][col] |= canVisited[row][col-1];
  13. if(canVisited[row][col]) ++count;
  14. }
  15. }
  16. return count;
  17. }

训练题:数组组成最小数

题目:http://t.cn/A6Vxkygn

  1. string minNumber(vector<int>& nums) {
  2. const auto size = nums.size();
  3. int len = 0; // nums的元素个数
  4. int maxLen; // 最长元素长度
  5. vector<string> strs; // 将数字都转化成string
  6. strs.reserve(size);
  7. // 将所有数字转化成string
  8. for(int i = 0, tmpLen; i < size; ++i) {
  9. strs.push_back(to_string(nums[i]));
  10. tmpLen = strs.back().size();
  11. maxLen = max(maxLen, tmpLen); // 记录最长元素
  12. len += tmpLen; // 统计拼接成的string的总长度
  13. }
  14. // 对字符串数组进行排序,tmpStr1、tmpStr2是为了避免排序比较中的str1+str2<str2+str1的低性能代码。
  15. string tmpStr1, tmpStr2;
  16. tmpStr1.reserve(maxLen << 1);
  17. tmpStr2.reserve(maxLen << 1);
  18. sort(strs.begin(), strs.end(), [&](string &a, string &b) -> bool {
  19. // "3"和"34":"334" < "343"
  20. // "12"和9: "129" < "912"
  21. tmpStr1.resize(0);
  22. tmpStr2.resize(0);
  23. tmpStr1.append(a).append(b);
  24. tmpStr2.append(b).append(a);
  25. return tmpStr1 < tmpStr2;
  26. });
  27. // 将已排好序的vector<string>拼接成string
  28. string ret;
  29. ret.reserve(len);
  30. for(const auto& str: strs) {
  31. ret.append(str);
  32. }
  33. return ret;
  34. }

训练题:构建乘积数组

题目:http://t.cn/A65aO44I
算法训练_数组 - 图2

  1. vector<int> constructArr(vector<int>& a) {
  2. const auto size = a.size();
  3. vector<int> ret(size,1);
  4. int left = 1, right = 1;
  5. for(int i = 1; i < size; ++i){
  6. ret[i] *= (left *= a[i - 1]);
  7. }
  8. for(int i = size - 2; i >=0; --i){
  9. ret[i] *= (right *= a[i + 1]);
  10. }
  11. return ret;
  12. }

训练题:找数字

场景一:数组中数字出现的次数

题目:http://t.cn/A6A2wJLJ

  1. vector<int> singleNumbers(vector<int>& nums) {
  2. int a = 0, b = 0; // 待找出的两个数为a、b
  3. int XOR = 0; // XOR = a ^ b
  4. int div = 1; // div为XOR二进制数中最右边第一个1。
  5. // XOR的最终结果为a 异或 b的结果,相同的两个数异或=0
  6. for(const auto& num : nums) XOR ^= num;
  7. // div为XOR二进制数中最右边第一个1。
  8. while(!(XOR & div)) div <<= 1;
  9. // div = (~XOR + 1) & XOR;
  10. // div = XOR & (-XOR); // 负数为补码,补码 = 反码 + 1。
  11. // div表示,a、b在这个位上是不同的。我们以此来将数组分成两组分别进行异或即可找出这两个数。
  12. for(const auto & num: nums) {
  13. if(num & div) a ^= num;
  14. else b ^= num;
  15. }
  16. return {a, b};
  17. }

场景二

题目:http://t.cn/A6qumpuV

  1. int singleNumber(vector<int>& nums) {
  2. // 保存所有数字在32位的每个位上1出现的次数
  3. unsigned short bits[32] = {0};
  4. char offset = 0;
  5. for(const auto& num : nums){
  6. for(char offset = 0; offset < 32; ++offset) {
  7. if(num & (1 << offset)) ++bits[offset];
  8. }
  9. }
  10. // 设任意位上的次数=n,则若n%3==0,则表示目标数字在该位置上是0,反之则为1。
  11. int fuck = 0;
  12. for(char idx = 0; idx < 32; ++idx){
  13. fuck |= (bits[idx] % 3) << idx;
  14. }
  15. return fuck;
  16. }

训练题:0~n-1中缺失的数

题目:http://t.cn/A6qjYvVx

  1. int missingNumber(vector<int>& nums) {
  2. int left = 0;
  3. // 二分查找法
  4. for(int right = nums.size() - 1, mid; left <= right;){
  5. mid = left + ((right - left) >> 1);
  6. if(nums[mid] == mid) left = mid + 1;
  7. else right = mid - 1;
  8. }
  9. return left;
  10. }

训练题:和为s的数

题目:http://t.cn/A6q3etqA

方法一:哈希表

  1. vector<int> twoSum(vector<int>& nums, int target) {
  2. set<int> container;
  3. for(const auto& num : nums){
  4. if(container.count(num)){
  5. return {target - num, num};
  6. }
  7. else container.insert(target - num);
  8. }
  9. return {};
  10. }

方法二:双指针

  1. vector<int> twoSum(vector<int>& nums, int target) {
  2. for(int left = 0, right = nums.size() - 1; left < right;){
  3. if(target - nums[left] == nums[right]) return {nums[left], nums[right]};
  4. else if(target - nums[left] > nums[right]) ++left;
  5. else --right;
  6. }
  7. return {};
  8. }

训练题:合并区间

题目:http://t.cn/A67wbdej

按区间的左元素升序排序,得到的区间序列,其中连续的区间肯定是在一块的。

  1. vector<vector<int>> merge(vector<vector<int>>& intervals) {
  2. if(intervals.size() < 2) return intervals;
  3. sort(intervals.begin(), intervals.end());
  4. vector<vector<int>> ret{intervals[0]};
  5. vector<int> *tmp = nullptr;
  6. for(const auto& vec : intervals)
  7. {
  8. tmp = &ret.back();
  9. if((*tmp)[1] >= vec[0]) (*tmp)[1] = max((*tmp)[1], vec[1]);
  10. else ret.push_back(vec);
  11. }
  12. return ret;
  13. }

训练题:搜索插入位置

题目:http://t.cn/A6hOM5MX

  1. int searchInsert(vector<int>& nums, int target) {
  2. int left = 0;
  3. for(int right = nums.size() - 1, mid; left < right;)
  4. {
  5. mid = ((left + right) >> 1);
  6. if(nums[mid] == target) return mid;
  7. else if(nums[mid] > target) right = mid;
  8. else left = mid + 1;
  9. }
  10. if(nums[left] < target) return left + 1;
  11. return left;
  12. }

训练题:中心下标

题目:http://t.cn/A65IN4vg

  1. // leftSum: 左边和
  2. // idx: 当前遍历索引,左边就是左边和
  3. // total: 数组总和
  4. // leftSum * 2 + nums[idx] == total
  5. int pivotIndex(vector<int>& nums) {
  6. int sum = 0, leftSum = 0;
  7. for(const auto& num : nums) sum += num;
  8. unsigned short size = nums.size();
  9. for(unsigned short idx = 0; idx < size; ++idx)
  10. {
  11. if((leftSum * 2) + nums[idx] == sum) return idx;
  12. leftSum += nums[idx];
  13. }
  14. return -1;
  15. }

训练题:奇数在偶数的前面

题目:http://t.cn/A6b1uvbE

方法一:双指针

  1. vector<int> exchange(vector<int>& nums) {
  2. if( nums.size() < 2 ) { return nums; }
  3. // 快速排序的partition分区思想。
  4. for( unsigned short left = -1, right = nums.size();; )
  5. {
  6. while( ++left < right && (nums[left] & 0x1) );
  7. while( --right > left && !(nums[right] & 0x1) );
  8. if( left < right ) { std::swap( nums[left], nums[right] ); }
  9. else { break; }
  10. }
  11. return nums;
  12. }

方法二:快慢指针

  • slow指向待插入奇数的位置。
  • fast指向找到的奇数 ```cpp

vector exchange(vector& nums) { if( nums.size() < 2 ) { return nums; }

  1. const auto size = nums.size();
  2. for( unsigned short slow = 0, fast = 0;; )
  3. {
  4. while( fast < size && !( nums[fast] & 0x1 ) ) { ++fast; }
  5. if( fast < size ) { std::swap( nums[fast++], nums[slow++] ); }
  6. else { break; }
  7. }
  8. return nums;

}

  1. <a name="FCNIY"></a>
  2. # 训练题:旋转数组的最小数字
  3. 题目:[http://t.cn/A6V4bma7](http://t.cn/A6V4bma7)
  4. ```cpp
  5. int minArray(vector<int>& numbers) {
  6. if(numbers.empty()) return INT_MIN;
  7. int left = 0, right = numbers.size() - 1, mid;
  8. for(;left < right;){
  9. mid = left + ((right - left) >> 1);
  10. if(numbers[mid] < numbers[right]) right = mid;
  11. else if(numbers[mid] == numbers[right]) right -= 1;
  12. else left = mid + 1;
  13. }
  14. return numbers[left];
  15. }

训练题:重复的数字

题目:http://t.cn/A62yf09c

方法一

遍历数组的过程中,让nums[idx]的值在nums[nums[idx]]位置上,即元素值和索引值相同。
时间复杂度:O(n)
空间复杂度:O(1)

  1. int findRepeatNumber(vector<int>& nums) {
  2. const auto size = nums.size();
  3. for( int idx = 0; idx < size; ++idx )
  4. {
  5. if( idx != nums[idx] )
  6. {
  7. if( nums[idx] == nums[nums[idx]] ) { return nums[idx]; }
  8. else { std::swap( nums[nums[idx]], nums[idx] ); }
  9. }
  10. }
  11. return -1;
  12. }

方法二:set集合

时间复杂度:O(n)
空间复杂度:O(n)

  1. int findRepeatNumber(vector<int>& nums) {
  2. set<int> shit;
  3. for( auto &num : nums )
  4. {
  5. if( shit.count( num ) ) { return num; }
  6. else { shit.insert( num ); }
  7. }
  8. return -1;
  9. }

训练题:超过一半的数字

题目:http://t.cn/A6V459Fx

方法一:排序

先排序,然后返回中间那个数,这里我们假设一定存在这样一个数。

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)

方法二:哈希

用一个哈希统计每个数出现的次数。

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  1. int MoreThanHalfNum_Solution( vector<int> numbers )
  2. {
  3. map<int, unsigned short> shit;
  4. const auto size = numbers.size() >> 1;
  5. for( const auto &num : numbers ) if( ++shit[num] > size ) { return num; }
  6. return -1;
  7. }

方法三

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  1. int MoreThanHalfNum_Solution( vector<int> numbers )
  2. {
  3. unsigned short size = numbers.size();
  4. // 遍历数组,当target与当前数不同,count-1,count=0,就将target赋值为当前数
  5. // 当target与当前数相同则count+1,遍历到最后一个数,target必定为目标数
  6. unsigned short count = 1; //
  7. int target = numbers[0]; // 当前数目
  8. for( unsigned short idx = 1; idx < size ; ++idx )
  9. {
  10. target == numbers[idx] ? ++count : --count;
  11. if( !count )
  12. {
  13. target = numbers[idx];
  14. count = 1;
  15. }
  16. }
  17. return target;
  18. }

训练题:两数之和

题目:http://t.cn/A6Vzz5Cd

  1. typedef int Value;
  2. typedef int Index;
  3. /**
  4. *
  5. * @param numbers int整型vector
  6. * @param target int整型
  7. * @return int整型vector
  8. */
  9. vector<int> twoSum( vector<int> &numbers, int target )
  10. {
  11. if( numbers.size() < 2 ) { return {}; }
  12. map<Value, Index> shit;
  13. int index2 = -1;
  14. for( int index1 = 0; index1 < numbers.size(); index1++ )
  15. {
  16. if( shit.find( target - numbers[index1] ) != shit.end() )
  17. {
  18. index2 = shit[target - numbers[index1]];
  19. return {index2 + 1, index1 + 1};
  20. }
  21. shit.insert( make_pair( numbers[index1], index1 ) );
  22. }
  23. return{};
  24. }

训练题:三数之和

题目:https://leetcode-cn.com/problems/3sum/

方法一:降维成两数之和

  1. vector<vector<int>> threeSum(vector<int>& nums) {
  2. if(nums.size() < 3) return {};
  3. sort(nums.begin(), nums.end());
  4. if(nums[0] > 0) return {};
  5. const auto size = static_cast<short>(nums.size());
  6. vector<vector<int>> ret;
  7. for(short first = 0; first < size - 2; ++first){
  8. // 先取第一个数,问题就变成在数组中找两数之和的问题,用双指针搞定。
  9. // 当前取的first和上一轮的相同,那这个first就重复了。
  10. if(first > 0 && nums[first] == nums[first - 1]) continue;
  11. int target = 0 - nums[first]; // 目标和
  12. int left = first + 1, right = size - 1; // 左右指针
  13. while(left < right){
  14. // 这里其实还
  15. if(nums[left] + nums[right] < target) ++left; // 右移左指针
  16. else if(nums[left] + nums[right] > target) --right; // 左移右指针
  17. else{ // 找到目标
  18. ret.push_back({nums[first], nums[left++], nums[right--]});
  19. // 排序之后,相等的数都相邻,找到目标之后,需要做去重操作。
  20. for(; left < right && nums[left] == nums[left-1]; ++left); // 当前的left指针和目标和的left指针相同,跳过
  21. for(; left < right &&nums[right] == nums[right+1]; --right);
  22. }
  23. }
  24. }
  25. return ret;
  26. }

训练题:最接近三数之和

题目:https://leetcode-cn.com/problems/3sum-closest/

同上理。

  1. int threeSumClosest(vector<int>& nums, int target) {
  2. sort(nums.begin(), nums.end());
  3. const auto size = nums.size();
  4. int ret = nums[0] + nums[1] + nums[2];
  5. if(ret == target) return ret;
  6. for(int first = 0; first < size - 2; ++first){
  7. if(first > 0 && nums[first] == nums[first - 1]) continue;
  8. for(int left = first+1, right = size - 1, tmpSum; left < right; ) {
  9. tmpSum = nums[first] + nums[left] + nums[right];
  10. if(tmpSum == target) return target;
  11. else {
  12. if(abs(ret - target) > abs(tmpSum - target)) ret = tmpSum;
  13. if(tmpSum > target) for(--right; left < right && nums[right] == nums[right+1]; --right);
  14. else for(++left; left < right && nums[left] == nums[left-1]; ++left);
  15. }
  16. }
  17. }
  18. return ret;
  19. }

训练题:最大累加和

题目:http://t.cn/A6qIcLYu

动态规划。
连续子数组的最大累加和肯定是以某个元素结尾。
我们设dp[i]是以i位置元素结尾的最大累加和。按dp思想,我们就要从dp[i-1]来求dp[i]。

算法训练_数组 - 图3

  1. int maxSubArray(vector<int>& nums) {
  2. int maxSum = nums[0]; // 当前的最大累加和。
  3. for(int i = 1; i < nums.size(); i++){
  4. // nums[i]存储以nums[i]元素结尾的连续最大累加和。
  5. // 如果nums[i - 1]<=0,则nums[i]的最大累加和就等于nums[i]元素,
  6. // 如果nums[i - 1]>0,则nums[i]的最大累加和=nums[i-1] + nums[i]
  7. nums[i] += max(nums[i - 1], 0);
  8. // 比较当前记录的最大累加和,和nums[i]位置处的最大累加和。
  9. maxSum = max(maxSum, nums[i]);
  10. }
  11. return maxSum;
  12. }

训练题:合并有序数组

题目:http://t.cn/A6VZoOZ2

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  1. void merge(int A[], int m, int B[], int n) {
  2. // 逆序遍历A、B数组。将最大者保存到tail中
  3. for( int tail = m + n - 1, tailA = m - 1, tailB = n - 1; tail >= 0; --tail )
  4. {
  5. if( tailA >= 0 && ( tailB < 0 || B[tailB] < A[tailA] ) )
  6. {
  7. A[tail] = A[tailA--];
  8. }
  9. else
  10. {
  11. A[tail] = B[tailB--];
  12. }
  13. }
  14. }

训练题:有序数组查找数字

二分查找算法

有序数组查找数字,就不得不想到二分查找法。
二分查找算法的使用心得,将left、right、mid当成二分搜索的动力,nums[mid]与target比较时,mid的赋值决定了搜索的方向(如果找到相同的,是往右边继续搜,还是往左边)。

  1. // nums是升序的,target是需要找的数字
  2. // 返回目标数字在数组nums中的索引,如果没有找到返回-1
  3. int search(vector<int>& nums, int target) {
  4. int left = 0, right = nums.size() - 1;
  5. for(int mid; left <= right;) {
  6. // 可选逻辑:先行判断,减少计算量
  7. if(nums[left] == target) return left;
  8. else if(nums[right] == target) return right;
  9. // 写成mid = (left + right) >> 1有整型溢出风险。
  10. // >>位移运算符的优先级很低,请加括号
  11. mid = left + ((right - left) >> 1);
  12. if(nums[mid] == target) return mid;
  13. else if(nums[mid] > target) right = mid - 1;
  14. else left = mid + 1;
  15. }
  16. // 到这一步,left > right,没有找到。
  17. return -1;
  18. }

算法变形一

  1. // nums是升序的,找到target从索引0开始的第一次出现时的索引,没有返回-1。
  2. // 找区域的左边界。
  3. int search(vector<int>& nums, int target) {
  4. int start = -1;
  5. for(int left = 0, right = nums.size() - 1, mid; left <= right;){
  6. mid = left + ((right - left) >> 1);
  7. if(nums[mid] == target) start = mid;
  8. if(nums[mid] >= target) right = mid - 1; // 找到相同的,继续往左边搜。
  9. else left = mid + 1;
  10. }
  11. return start;
  12. }

算法变形二

  1. // nums是升序的,找到target从末尾往前第一次出现的索引。不存在返回-1
  2. // 找区域的右边界。
  3. int search(vector<int>& nums, int target) {
  4. int end = -1;
  5. for(int left = 0, right = nums.size() - 1, mid; left <= right;){
  6. mid = left + ((right - left) >> 1);
  7. if(nums[mid] == target) end = mid;
  8. if(nums[mid] <= target) left = mid + 1; // 找到相同的继续往右边搜。
  9. else right = mid - 1;
  10. }
  11. return end;
  12. }

场景一:查找数字

题目:http://t.cn/A6V45X5T

  1. int search(vector<int>& nums, int target) {
  2. int targetIdx = -1;
  3. for( int left = 0, right = nums.size() - 1, mid; left <= right; )
  4. {
  5. mid = left + ((right - left) >> 1);
  6. if(nums[mid] == target) ret = mid;
  7. if(nums[mid] >= target) right = mid - 1;
  8. else { left = mid + 1; }
  9. }
  10. return targetIdx;
  11. }

场景二:搜索插入位置

题目:http://t.cn/A6hOM5MX

  1. int searchInsert(vector<int>& nums, int target) {
  2. int targetIdx = 0;
  3. for(int left = 0, right = nums.size() - 1, mid; left <= right;){
  4. mid = left + ((right - left) >> 1);
  5. if(nums[mid] == target) return mid;
  6. if(nums[mid] > target) targetIdx = mid;
  7. else targetIdx = mid + 1;
  8. if(nums[mid] > target) right = mid - 1;
  9. else left = mid + 1;
  10. }
  11. return targetIdx;
  12. }

场景三:统计数字个数

题目:http://t.cn/A6qjjjXp

  1. int search(vector<int>& nums, int target) {
  2. // 通过二分查找法找到target区域的左右边界
  3. int targetLeftIdx = -1, targetRightIdx = -1;
  4. // 找左边界
  5. for(int left = 0, right = nums.size() - 1, mid; left <= right;){
  6. mid = left + ((right - left) >> 1);
  7. if(nums[mid] == target) targetLeftIdx = mid;
  8. if(nums[mid] >= target) right = mid - 1;
  9. else left = mid + 1;
  10. }
  11. // 找到右边界
  12. for(int left = 0, right = nums.size() - 1, mid; left <= right;){
  13. mid = left + ((right - left) >> 1);
  14. if(nums[mid] == target) targetRightIdx = mid;
  15. if(nums[mid] <= target) left = mid + 1;
  16. else right = mid - 1;
  17. }
  18. // 没有找到target
  19. if(targetLeftIdx == -1) return 0;
  20. // 找到target,索引相减 + 1
  21. return targetRightIdx - targetLeftIdx + 1;
  22. }

训练题:移动零

题目:https://leetcode-cn.com/problems/move-zeroes/

  1. void moveZeroes(vector<int>& nums) {
  2. if(nums.size() < 2) return;
  3. const auto size = nums.size();
  4. // 双指针
  5. // p_0: 指向从头开始的第一个0。
  6. // p_num: 指向p_0之后的第一个非零数字。
  7. int p_0 = 0, p_num = 0;
  8. // 指向第一个0
  9. for(; p_0 < size && nums[p_0] != 0; ++p_0);
  10. p_num = p_0 + 1;
  11. while(true){
  12. // 指向第一个0
  13. for(; p_0 < size && nums[p_0] != 0; ++p_0);
  14. // 指向p_0之后的第一个非0
  15. for(; p_num < size && nums[p_num] == 0; ++p_num);
  16. // 交换了之后,p_0肯定是非0,p_num肯定是0。
  17. // 因此将p_0和p_num右移一位
  18. if(p_0 < size && p_num < size) swap(nums[p_0++], nums[p_num++]);
  19. else break;
  20. }
  21. }
  1. void moveZeroes(vector<int>& nums) {
  2. const auto size = nums.size();
  3. int left = 0, right = 0;
  4. // left: left指向第一个非0
  5. // right: right指向第一个0。
  6. for(int left = 0, right = 0; right < size; ++right){
  7. if(nums[right]) swap(nums[right], nums[left++]);
  8. }
  9. }

训练题:两个正序数组的中位数

题目:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

方法一:模拟法

遍历中位数的位置。

  1. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  2. if(nums1.empty() && nums2.empty()) return 0;
  3. const auto size1 = nums1.size();
  4. const auto size2 = nums2.size();
  5. // 设:
  6. // nums1 = [1, 3, 5];
  7. // nums2 = [2, 4, 6];
  8. // remainCount = 4;
  9. // 按从小到大的顺序,移动tmpIdx1、tmpIdx2索引,得:
  10. // tmpIdx1 = 2,即移动到了tmpIdx1-1=1索引处,为3。
  11. // tmpIdx2 = 2,即移动到了tmpIdx2-1=1索引处,为4。
  12. int remainCount = ((size1 + size2) >> 1) + 1;
  13. int tmpIdx1 = 0, tmpIdx2 = 0;
  14. for(; tmpIdx1 < size1 && tmpIdx2 < size2 && remainCount ; --remainCount){
  15. if(nums1[tmpIdx1] < nums2[tmpIdx2]) ++tmpIdx1;
  16. else ++tmpIdx2;
  17. }
  18. if(remainCount && tmpIdx1 < size1) tmpIdx1 += remainCount;
  19. if(remainCount && tmpIdx2 < size2) tmpIdx2 += remainCount;
  20. // 若为奇数,则取tmpIdx1-1和tmpIdx2-1索引处更大者。
  21. if((size1 + size2) & 1) {
  22. if(tmpIdx1 > 0 && tmpIdx2 > 0) return max(nums1[tmpIdx1-1], nums2[tmpIdx2-1]);
  23. if(tmpIdx1 > 0) return nums1[tmpIdx1-1];
  24. return nums2[tmpIdx2-1];
  25. }
  26. else{
  27. // 若为偶数,则取tmpIdx-1、tmpIdx1-2、tmpIdx2-1、tmpIdx2-2中最大的两个。
  28. priority_queue<int> q;
  29. if(tmpIdx1 > 0) q.push(nums1[tmpIdx1-- - 1]);
  30. if(tmpIdx1 > 0) q.push(nums1[tmpIdx1 - 1]);
  31. if(tmpIdx2 > 0) q.push(nums2[tmpIdx2-- - 1]);
  32. if(tmpIdx2 > 0) q.push(nums2[tmpIdx2 - 1]);
  33. int mid1 = q.top();
  34. q.pop();
  35. return (mid1 + q.top()) / 2.0;
  36. }
  37. }

方法二:模拟法

  1. class Solution {
  2. public:
  3. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  4. if(nums1.empty() && nums2.empty()) return 0;
  5. const auto size1 = nums1.size();
  6. const auto size2 = nums2.size();
  7. int remainCount = (size1 + size2) / 2 + 1;
  8. int tmpIdx1 = 0, tmpIdx2 = 0;
  9. for(; tmpIdx1 < size1 && tmpIdx2 < size2 && remainCount ; --remainCount){
  10. if(nums1[tmpIdx1] < nums2[tmpIdx2]) ++tmpIdx1;
  11. else ++tmpIdx2;
  12. }
  13. if(remainCount && tmpIdx1 < size1) tmpIdx1 += remainCount;
  14. if(remainCount && tmpIdx2 < size2) tmpIdx2 += remainCount;
  15. if((size1 + size2) & 1) {
  16. if(tmpIdx1 > 0 && tmpIdx2 > 0) return max(nums1[tmpIdx1-1], nums2[tmpIdx2-1]);
  17. if(tmpIdx1 > 0) return nums1[tmpIdx1-1];
  18. return nums2[tmpIdx2-1];
  19. }
  20. else{
  21. // 与上一种方法的区别主要在于偶数情况时的动作。
  22. // 去tmpIdx1-1和tmpIdx1-2的情况有两种
  23. // 一、tmpIdx2无效
  24. // 二、tmpIdx>1且tmpIdx2-1<tmpIdx1-2
  25. if(tmpIdx1 > 0 && tmpIdx2 == 0 || tmpIdx1 > 1 && tmpIdx2 > 0 && nums2[tmpIdx2-1] <= nums1[tmpIdx1-2])
  26. return (nums1[tmpIdx1-1] + nums1[tmpIdx1-2]) / 2.0;
  27. else if(tmpIdx2 > 0 && tmpIdx1 == 0 || tmpIdx2 > 1 && tmpIdx1 > 0 && nums1[tmpIdx1-1] <= nums2[tmpIdx2-2])
  28. return (nums2[tmpIdx2-1] + nums2[tmpIdx2-2]) / 2.0;
  29. return (nums1[tmpIdx1 - 1] + nums2[tmpIdx2-1]) / 2.0;
  30. }
  31. }
  32. };

方法三:模拟法

  1. double findMedianSortedArrays( vector<int> nums1, vector<int> nums2 )
  2. {
  3. const auto size1 = static_cast<int>( nums1.size() );
  4. const auto size2 = static_cast<int>( nums2.size() );
  5. bool even = ( ( size1 + size2 ) & 1 ) == 0;
  6. if( size1 == 0 && size2 == 0 ) { return 0; }
  7. if( size1 == 1 && size2 == 1 ) { return ( nums1[0] + nums2[0] ) / 2.0; }
  8. if( size1 == 0 ) { return even ? ( nums2[size2 >> 1] + nums2[( size2 - 1 ) >> 1] ) / 2.0 : nums2[( size2 >> 1 )] ; }
  9. if( size2 == 0 ) { return even ? ( nums1[size1 >> 1] + nums1[( size1 - 1 ) >> 1] ) / 2.0 : nums1[( size1 >> 1 )] ; }
  10. int remainCount = ( size1 + size2 ) / 2 + 1;
  11. int val1 = 0, val2 = 0;
  12. int tmpIdx1 = 0, tmpIdx2 = 0;
  13. for( ; tmpIdx1 < size1 && tmpIdx2 < size2 && remainCount; --remainCount )
  14. {
  15. if( even ) { val2 = val1; }
  16. if( nums1[tmpIdx1] < nums2[tmpIdx2] ) { val1 = nums1[tmpIdx1++]; }
  17. else { val1 = nums2[tmpIdx2++]; }
  18. }
  19. for( ; tmpIdx1 < size1 && remainCount; --remainCount, ++tmpIdx1 )
  20. {
  21. if( even ) { val2 = val1; }
  22. val1 = nums1[tmpIdx1];
  23. }
  24. for( ; tmpIdx2 < size2 && remainCount; --remainCount, ++tmpIdx2 )
  25. {
  26. if( even ) { val2 = val1; }
  27. val1 = nums2[tmpIdx2];
  28. }
  29. if( !even ) { return val1; }
  30. else { return ( val1 + val2 ) / 2.0; }
  31. }