通关算法题之 ⌈数组⌋ 上
创始人
2024-02-09 03:19:37

滑动窗口

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

输入: s = "abcabcbb"
输出: 3 

这就是变简单了,连 needvalid 都不需要,而且更新窗口内数据也只需要简单的更新计数器 window 即可。当 window[c] 值大于 1 时,说明窗口中存在重复字符,不符合条件,就该移动 left 缩小窗口了。

唯一需要注意的是,在哪里更新结果 res 呢?我们要的是最长无重复子串,哪一个阶段可以保证窗口中的字符串是没有重复的呢?

这里和之前不一样,要在收缩窗口完成后更新 res,因为窗口收缩的 while 条件是存在重复元素的,换句话说收缩完成后一定保证窗口中没有重复。

class Solution {
public:int lengthOfLongestSubstring(string s) {unordered_map window;int left = 0, right = 0, res = 0;while(right < s.size()){char c = s[right];right++;window[c]++;while(window[c] > 1){char d = s[left];left++;window[d]--;}res = max(res, right - left);}return res;}
};

209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target ,找出该数组中满足其和 ≥ target 的长度最小的 连续子数组[numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2

不过需要强调的是,题目说了 nums 数组中的元素都是正数,有了这个前提才能使用滑动窗口算法,因为窗口扩大时窗口内元素之和必然增大,窗口缩小时窗口内元素之和必然减小。

如果 nums 数组中包含负数,则窗口扩大时元素和不见得就增大,窗口缩小时元素和不见得就减小,这种情况就不能单纯使用滑动窗口技巧了,可能需要混合动态规划和单调队列来做。

class Solution {
public:int minSubArrayLen(int target, vector& nums) {int left = 0, right = 0, sum = 0, res = INT_MAX;while(right < nums.size()){sum += nums[right];right++;while(sum >= target && left < right){res = min(res, right - left);sum -= nums[left];left++;}}return res == INT_MAX ? 0 : res;}
};

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t ,返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串""

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

滑动窗口的思路:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解,也就是最短的覆盖子串。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动,这就是「滑动窗口」这个名字的来历。

初始状态:

image-20220520115217917

增加 right,直到窗口 [left, right) 包含了 T 中所有字符:

image-20220520115349776

现在开始增加 left,缩小窗口 [left, right)

image-20220520115450943

直到窗口中的字符串不再符合要求,left 不再继续移动:

image-20220520115526871

之后重复上述过程,先移动 right,再移动 left…… 直到 right 指针到达字符串 S 的末端,算法结束。

首先,初始化 windowneed 两个哈希表,记录窗口中的字符和需要凑齐的字符:

unordered_map need, window;
for (char c : t) need[c]++;

然后,使用 leftright 变量初始化窗口的两端,不要忘了,区间 [left, right)左闭右开的,所以初始情况下窗口没有包含任何元素:

int left = 0, right = 0;
int valid = 0; 
while (right < s.size()) {// 开始滑动
}

其中 valid 变量表示窗口中满足 need 条件的字符个数,如果 validneed.size 的大小相同,则说明窗口已满足条件,已经完全覆盖了串 T

如果一个字符进入窗口,应该增加 window 计数器;如果一个字符将移出窗口的时候,应该减少 window 计数器;当 valid 满足 need 时应该收缩窗口;应该在收缩窗口的时候更新最终结果。

string minWindow(string s, string t) {unordered_map need, window;for (char c : t){need[c]++;}int left = 0, right = 0, valid = 0; // valid表示窗口中满足 need 条件的字符个数// 记录最小覆盖子串的起始索引及长度int start = 0, len = INT_MAX;while (right < s.size()) {// c 是将移入窗口的字符char c = s[right];// 扩大窗口right++;// 进行窗口内数据的一系列更新if (need.count(c)) {window[c]++;if (window[c] == need[c]) valid++;}// 判断左侧窗口是否要收缩while (valid == need.size()) {// 在这里更新最小覆盖子串if (right - left < len) {start = left;len = right - left;}// d 是将移出窗口的字符char d = s[left];// 缩小窗口left++;// 进行窗口内数据的一系列更新if (need.count(d)) {if (window[d] == need[d]) valid--;window[d]--;}                    }}// 返回最小覆盖子串return len == INT_MAX ? "" : s.substr(start, len);
}

需要注意的是,当我们发现某个字符在 window 的数量满足了 need 的需要,就要更新 valid,表示有一个字符已经满足要求,而且两次对窗口内数据的更新操作是完全对称的。

valid == need.size() 时,说明 T 中所有字符已经被覆盖,已经得到一个可行的覆盖子串,现在应该开始收缩窗口了,以便得到「最小覆盖子串」。

移动 left 收缩窗口时,窗口内的字符都是可行解,所以应该在收缩窗口的阶段进行最小覆盖子串的更新,以便从可行解中找到长度最短的最终结果。

567. 字符串的排列

给你两个字符串 s1s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false

输入:s1 = "ab" s2 = "eidbaooo"
输出:true
输入:s1= "ab" s2 = "eidboaoo"
输出:false

注意哦,输入的 s1 是可以包含重复字符的,所以这个题难度不小。

这种题目,是明显的滑动窗口算法,相当给你一个 S 和一个 T,请问你 S 中是否存在一个子串,包含 T 中所有字符且不包含其他字符

对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变两个地方:

1、本题移动 left 缩小窗口的时机是窗口大小大于 t.size() 时,应为排列嘛,显然长度应该是一样的。

2、当发现 valid == need.size() 时,就说明窗口中就是一个合法的排列,所以立即返回 true

至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。

class Solution {
public:// 判断 s2 中是否存在 s1 的排列bool checkInclusion(string s1, string s2) {unordered_map need, window;for(char c : s1){need[c]++;}int left = 0, right = 0, valid = 0;while(right < s2.size()){char c = s2[right];right++;if(need.count(c)){window[c]++;if(window[c] == need[c]) valid++;}// 判断左侧窗口是否要收缩while(valid == need.size()){// 判断是否找到了合法的子串if(right - left == s1.size()) return true;char d = s2[left];left++;if(need.count(d)){if(window[d] == need[d]) valid--;window[d]--;}}}// 未找到符合条件的子串return false;}
};

438. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]

所谓的字母异位词就是排列,相当于输入一个串 S,一个串 T,找到 S 中所有 T 的排列,返回它们的起始索引。跟寻找字符串的排列一样,只是找到一个合法异位词(排列)之后将起始索引加入 res 即可。

class Solution {
public:vector findAnagrams(string s, string p) {unordered_map need, window;for(char c : p) need[c]++;int left = 0, right = 0, valid = 0;vector res;while(right < s.size()){char c = s[right];right++;if(need.count(c)){window[c]++;if(window[c] == need[c]) valid++;}// 判断左侧窗口是否要收缩while(valid == need.size()){// 当窗口符合条件时,把起始索引加入 resif(right - left == p.size()) res.push_back(left);char d = s[left];left++;if(need.count(d)){if(need[d] == window[d]) valid--;window[d]--;}}}return res;}
};

剑指 Offer 57 - II. 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。

输入:target = 9
输出:[[2,3,4],[4,5]]
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]

双指针,滑动窗口。

class Solution {
public:vector> findContinuousSequence(int target) {int left = 1, right = 1, sum = 0;vector> res;deque add;while(left <= target / 2){sum += right;add.push_back(right);right++;while(sum >= target){if(sum == target){res.push_back(vector(add.begin(), add.end()));}sum -= left;add.pop_front();left++;}}return res;      }
};

数组遍历/旋转

BM97 旋转数组

一个数组A中存有 n 个整数,在不允许使用另外数组的前提下,将每个整数循环向右移 M( M >=0)个位置,即将A中的数据由(A0 A1 ……AN-1 )变换为(AN-M …… AN-1 A0 A1 ……AN-M-1 )(最后 M 个数循环移至最前面的 M 个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?

输入:6,2,[1,2,3,4,5,6]
输出:[5,6,1,2,3,4]

使用三次翻转,思路:

  • step 1:因为m可能大于n,因此需要对n取余,因为每次长度为n的旋转数组相当于没有变化;
  • step 2:第一次将整个数组翻转,得到数组的逆序,它已经满足了右移的整体出现在了左边;
  • step 3:第二次就将左边的m个元素单独翻转,因为它虽然移到了左边,但是逆序了;
  • step 4:第三次就将右边的n−m个元素单独翻转,因此这部分也逆序了;
class Solution {
public:vector solve(int n, int m, vector& a) {//取余,因为每次长度为n的旋转数组相当于没有变化m = m % n; //第一次逆转全部数组元素reverse(a.begin(), a.end()); //第二次只逆转开头m个reverse(a.begin(), a.begin() + m); //第三次只逆转结尾m个reverse(a.begin() + m, a.end()); return a;}
};

48. 旋转图像

给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

img
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]

旋转二维矩阵的难点在于将「行」变成「列」,将「列」变成「行」,而只有按照对角线的对称操作是可以轻松完成这一点的,对称操作之后就很容易发现规律了。

我们可以先将 n x n 矩阵 matrix 按照左上到右下的对角线进行镜像对称:

image-20220521115152062

然后再对矩阵的每一行进行反转:

image-20220521115345798

发现结果就是 matrix 顺时针旋转 90 度的结果:

image-20220521115523512
class Solution {
public:void rotate(vector>& matrix) {int n = matrix.size();for(int i = 0; i < n; i++){for(int j = 0; j < i; j++){swap(matrix[i][j], matrix[j][i]);}}for(int i = 0; i < n; i++){reverse(matrix[i].begin(), matrix[i].end());}}
};

那如何将矩阵逆时针旋转 90 度呢

思路是类似的,只要通过另一条对角线镜像对称矩阵,然后再反转每一行,就得到了逆时针旋转矩阵的结果:

image-20220521115657715
class Solution {
public:void rotate(vector>& matrix) {int n = matrix.size();for(int i = 0; i < n; i++){for(int j = 0; j < n - i; j++){swap(matrix[i][j], matrix[n - j - 1][n - i - 1]);}}for(int i = 0; i < n; i++){reverse(matrix[i].begin(), matrix[i].end());}}
};

54. 螺旋矩阵

给你一个 m n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

Oj35qK.jpg
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]

解题的核心思路是按照右、下、左、上的顺序遍历数组,并使用四个变量圈定未遍历元素的边界。

随着螺旋遍历,相应的边界会收缩,直到螺旋遍历完整个数组:

class Solution {
public:vector spiralOrder(vector>& matrix) {int m = matrix.size(), n = matrix[0].size();int upper = 0, lower = m - 1, left = 0, right = n - 1;vector res;// res.size() == m * n 则遍历完整个数组while(res.size() < m * n){// 在顶部从左向右遍历if(upper <= lower){for(int j = left; j <= right; j++){res.push_back(matrix[upper][j]);}// 上边界下移upper++;}if(left <= right){// 在右侧从上向下遍历for(int i = upper; i <= lower; i++){res.push_back(matrix[i][right]);}// 右边界左移right--;}if(upper <= lower){// 在底部从右向左遍历for(int j = right; j >= left; j--){res.push_back(matrix[lower][j]);}// 下边界上移lower--;}if(left <= right){// 在左侧从下向上遍历for(int i = lower; i >= upper; i--){res.push_back(matrix[i][left]);}// 左边界右移left++;}}return res;}
};

59. 螺旋矩阵II

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

Oj35qK.jpg
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

和上一道题类似的思路:

class Solution {
public:vector> generateMatrix(int n) {vector> res(n, vector(n));int upper = 0, lower = n - 1, left = 0, right = n - 1;int num = 1;while(num <= n * n){if(upper <= lower){for(int j = left; j <= right; j++){res[upper][j] = num++;}upper++;}if(left <= right){for(int i = upper; i <= lower; i++){res[i][right] = num++;}right--;}if(upper <= lower){for(int j = right; j >= left; j--){res[lower][j] = num++;}lower--;}if(left <= right){for(int i = lower; i >= upper; i--){res[i][left] = num++;}left++;}}return res;}
};

179. 最大数

给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。**注意:**输出结果可能非常大,所以你需要返回一个字符串而不是整数。

输入:nums = [10,2]
输出:"210"

此题求拼接起来的最小数字,本质上是一个排序问题。设数组nums中任意两数字的字符串为x和y,则规定排序判断规则为:若拼接字符串x + y > y + x,则x“大于”y;反之,若x + y < y + x,则x"小于”y。x“小于”y代表:排序完成后,数组中x应在y左边;“大于”则反之。根据以上规则,套用任何排序方法对nums执行排序即可。

class Solution {
public:string largestNumber(vector& nums) {vector strs;string res;for(int num : nums){strs.push_back(to_string(num));}sort(strs.begin(), strs.end(), [&](string a, string b){return a + b > b + a;});for(string str : strs){res += str;}//避免全部为0的情形return res[0] == '0' ? "0" : res;}
};

剑指 Offer 66. 构建乘积数组

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积,即B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

分别迭代计算下三角和上三角两部分的乘积,再相乘,即可 不使用除法 就获得结果。

image-20220628162754163
class Solution{
public:vector constructArr(vector &a){vector res(a.size(), 1);int left = 1;	// 左侧乘积int right = 1;	// 右侧乘积for (int i = 0; i < a.size(); ++i){//左侧的乘积res[i] *= left; left *= a[i];//右侧的乘积res[a.size() - 1 - i] *= right; right *= a[a.size() - 1 - i];}return res;}
};

66. 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。你可以假设除了整数 0 之外,这个整数不会以零开头。

输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。

其实就是一个数的各位储存在数组里,然后要按照整数加法的法则来计算,然后同样返回一个数组:

class Solution {
public:vector plusOne(vector& digits) {int len = digits.size();for(int i = len - 1; i >= 0; i --){digits[i] ++;digits[i] %= 10;if(digits[i] != 0){return digits;}}digits = vector(len + 1);digits[0] = 1;return digits;}
};

杨辉三角

118. 杨辉三角

给定一个非负整数 *numRows,*生成「杨辉三角」的前 numRows 行,在「杨辉三角」中,每个数是它左上方和右上方的数的和。

image-20220703141313905
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
class Solution {
public:vector> generate(int numRows) {vector> res(numRows);for (int i = 0; i < numRows; ++i) {res[i].resize(i + 1);res[i][0] = res[i][i] = 1;for (int j = 1; j < i; ++j) {res[i][j] = res[i - 1][j] + res[i - 1][j - 1];}}return res;}
};

119. 杨辉三角 II

给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。在「杨辉三角」中,每个数是它左上方和右上方的数的和。

输入: rowIndex = 3
输出: [1,3,3,1]

由于只要输出指定的某一行,故只需保存当前行的前一行结果即可。

class Solution {
public:vector getRow(int rowIndex) {if(rowIndex == 0) return {1};if(rowIndex == 1) return {1, 1};vector res(2, 1);for(int i = 2; i <= rowIndex; i++){vector tmp(i + 1, 1);for(int j = 1; j < i; j++){tmp[j] = res[j - 1] + res[j];}swap(res, tmp);}return res;}
};

870. 优势洗牌(田忌赛马)

给定两个大小相等的数组 nums1nums2nums1 相对于 nums2 的优势可以用满足 nums1[i] > nums2[i] 的索引 i 的数目来描述。返回 nums1任意排列,使其相对于 nums2 的优势最大化。

输入:nums1 = [2,7,11,15], nums2 = [1,10,4,11]
输出:[2,11,7,15]

田忌赛马算法实现。比得过最弱的,则正常放置;比不过,则用来消耗最强的。

class Solution {
public:vector advantageCount(vector& nums1, vector& nums2) {int n = nums1.size();vector idx(n), res(n);// 给数组递增赋值iota(idx.begin(), idx.end(), 0);// 按照nums2的顺序给idx排序sort(idx.begin(), idx.end(), [&](int i, int j){return nums2[i] < nums2[j];});sort(nums1.begin(), nums1.end());int left = 0, right = n - 1;for(int i = 0; i < n; i++){if(nums1[i] > nums2[idx[left]]){// 比得过最小的,则正常放置res[idx[left]] = nums1[i];left++;}else{// 比不过最小的,则用来消耗最强的res[idx[right]] = nums1[i];right--;}}return res;}
};

索引和数组元素对应

287. 寻找重复数

给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。

输入:nums = [1,3,4,2,2]
输出:2
输入:nums = [3,1,3,4,2]
输出:3

由于数组中的元素都是正数,且都在1到n 的范围内,故可以使用数组的下标作为辅助。

将和数组元素值相同的数组下标中对应的值置为其对应的负数,这样,当两个相同的元素映射到同一个位置,会发现元素值已经为负数了,既该值为重复的值。由于有映射负数的步骤,故使用数组元素时,需要使用其绝对值。

class Solution {
public:int findDuplicate(vector& nums) {for(int num : nums){if(nums[abs(num)] < 0){return abs(num);}else{nums[abs(num)] *= -1;}}return 0;}
};

442. 数组中重复的数据

给你一个长度为 n 的整数数组 nums ,其中 nums 的所有整数都在范围 [1, n] 内,且每个整数出现 一次两次 。请你找出所有出现 两次 的整数,并以数组形式返回。你必须设计并实现一个时间复杂度为 O(n) 且仅使用常量额外空间的算法解决此问题。

输入:nums = [4,3,2,7,8,2,3,1]
输出:[2,3]

和上一题一样的思路,但是需要注意索引的取值范围。

class Solution {
public:vector findDuplicates(vector& nums) {vector res;for(int num : nums){if(nums[abs(num) - 1] < 0){res.push_back(abs(num));}else{nums[abs(num) - 1] *= -1;}}return res;}
};

448. 找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

输入:nums = [4,3,2,7,8,2,3,1]
输出:[5,6]

nums 中的元素取值都在 [1, n] 之间,即元素本身就和索引成一一映射关系,所以可以用 nums 本身模拟哈希集合的效果。那么最终缺失的那些元素对应的索引就不会被标记,我们也就可以把缺失的那些元素找出来。

注意:用乘 -1 的方式给索引做标记的前提是,nums 中的元素都为正数,不能包含 0,否则无法区分该索引是否被标记。

class Solution {
public:vector findDisappearedNumbers(vector& nums) {for(int num : nums){// 注意索引,元素大小从 1 开始,有一位索引偏移if(nums[abs(num) - 1] < 0) continue; // 索引重复出现nums[abs(num) - 1] *= -1; // 把索引 num - 1 置为负数}vector res;for(int i = 0; i < nums.size(); i++){if(nums[i] > 0){// 说明没有元素和这个索引对应,即找到一个缺失元素res.push_back(i + 1);}}return res;}
};

334. 递增的三元子序列

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false 。

输入:nums = [1,2,3,4,5]
输出:true
解释:任何 i < j < k 的三元组都满足题意
输入:nums = [5,4,3,2,1]
输出:false
解释:不存在满足题意的三元组

把所有的数分成了三部分,一部分小于等于 first,中间部分小于等于 second,以及最后的一部分大于second。只要出现了第三部分,就满足条件,返回true;否则,跳出循环后,返回false。

class Solution {
public:bool increasingTriplet(vector& nums) {int first = INT_MAX;int second = INT_MAX;for(int& num : nums){//不停的将后面的数据分成三部分if(num <= first){//先更新firstfirst = num;}else if(num <= second){//然后才能更新secondsecond = num;}else if(num > second){//则出现大于second的数据时,直接返回truereturn true;}}return false;}
};

剑指 Offer 51. 数组中的逆序对(难)

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

输入: [7,5,6,4]
输出: 5

归并排序过程中会将数组划分成最小为1个元素的子数组,然后依次比较子数组的每个元素的大小,依次取出较小的一个合并成大的子数组。这里我们也可以用相同的方法划分,划分之后相邻一个元素的子数组就可以根据大小统计逆序对,而不断往上合并的时候,因为已经排好序了,我们逆序对可以往上累计。我们主要有以下三个阶段。

具体做法:

  • step 1: 划分阶段:将待划分区间从中点划分成两部分,两部分进入递归继续划分,直到子数组长度为1.
  • step 2: 排序阶段:使用归并排序递归地处理子序列,同时统计逆序对,因为在归并排序中,我们会依次比较相邻两组子数组各个元素的大小,并累计遇到的逆序情况。而对排好序的两组,右边大于左边时,它大于了左边的所有子序列,基于这个性质我们可以不用每次加1来统计,减少运算次数。
  • step 3: 合并阶段:将排好序的子序列合并,同时累加逆序对。
class Solution {
public:int reversePairs(vector& nums) {int n = nums.size();vector res(n);return mergeSort(0, n - 1, nums, res);}int mergeSort(int left, int right, vector& nums, vector& tmp){// 停止划分if(left >= right) return 0;// 取中间int mid = (left + right) / 2;// 左右划分合并int res = mergeSort(left, mid, nums, tmp) + mergeSort(mid + 1, right, nums, tmp);int i = left, j = mid + 1;for(int k = left; k <= right; k++){tmp[k] = nums[k];}for(int k = left; k <= right; k++){if(i == mid + 1){nums[k] = tmp[j++];}else if(j == right + 1 || tmp[i] <= tmp[j]){nums[k] = tmp[i++];}else{ // 左边比右边大,答案增加nums[k] = tmp[j++];// 统计逆序对res += mid - i + 1;}}return res;}
};

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...