July 11 - 274. H 指数;
July 12 - 275. H 指数 Ⅱ;
July 13 - 218. 天际线问题;
July 14 - 1818. 绝对差值和;
July 15 - 1846. 减小和重新排列数组后的最大元素;
July 16 - 剑指 Offer 53 - I. 在排序数组中查找数字 I;
July 17 - 剑指 Offer 42. 连续子数组的最大和;
July 18 - 面试题 10.02. 变位词组;
July 19 - 1838. 最高频元素的频数;
July 20 - 1877. 数组中最大数对和的最小值
July 11 - 274. H 指数 描述 给定一位研究者论文被引用次数的数组(被引用次数是非负整数)。编写一个方法,计算出研究者的 h 指数。
h 指数的定义:h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 N - h 篇论文每篇被引用次数 不超过 h 次。
例如:某人的 h 指数是 20,这表示他已发表的论文中,每篇被引用了至少 20 次的论文总共有 20 篇。
样例 1 2 3 4 输入:citations = [3 ,0 ,6 ,1 ,5 ] 输出:3 解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3 , 0 , 6 , 1 , 5 次。 由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3 。
1 提示:如果 h 有多种可能的值,h 指数是其中最大的那个。
思路 首先我们可以将初始的 H 指数 h 设为 0,然后将引用次数排序,并且对排序后的数组从大到小遍历。
根据 H 指数的定义,如果当前 H 指数为 h 并且在遍历过程中找到当前值 citations[i]>h,则说明我们找到了一篇被引用了至少 h+1 次的论文,所以将现有的 h 值加 1。继续遍历直到 h 无法继续增大。最后返回 h 作为最终答案。
时间复杂度:O(nlogn),其中 n 为数组 citations 的长度。即为排序的时间复杂度。
空间复杂度:O(logn),其中 n 为数组 citations 的长度。即为排序的空间复杂度。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int hIndex (vector<int >& citations) { int h = 0 ; sort (citations.rbegin (),citations.rend ()); for (auto a:citations){ if (a>h){ h++; } else { break ; } } return h; } };
July 12 - 275. H 指数 Ⅱ 描述 这是 H 指数 的延伸题目,本题中的 citations
数组是保证有序的。
优化算法到对数时间复杂度。
思路 由于数组 citations 已经按照升序排序,因此可以使用二分查找。
设查找范围的初始左边界 left 为 0, 初始右边界 right 为 n−1,其中 n 为数组 citations 的长度。每次在查找范围内取中点 mid,则有 n−mid 篇论文被引用了至少 citations[mid] 次。如果在查找过程中满足 citations[mid]≥n−mid,则移动右边界 right,否则移动左边界 left。
时间复杂度:O(logn),其中 n 为数组 citations 的长度。二分查找的时间复杂度为 O(logn)。
空间复杂度:O(1)。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int hIndex (vector<int >& citations) { int n = citations.size (); int left = 0 , right = n - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (citations[mid] >= n - mid) { right = mid - 1 ; } else { left = mid + 1 ; } } return n - left; } };
July 13 - 218. 天际线问题
July 14 - 1818. 绝对差值和 描述 给你两个正整数数组 nums1 和 nums2 ,数组的长度都是 n 。
数组 nums1 和 nums2 的 绝对差值和 定义为所有 |nums1[i] - nums2[i]|(0 <= i < n)的 总和(下标从 0 开始)。
你可以选用 nums1 中的 任意一个 元素来替换 nums1 中的 至多 一个元素,以 最小化 绝对差值和。
在替换数组 nums1 中最多一个元素 之后 ,返回最小绝对差值和。因为答案可能很大,所以需要对 109 + 7 取余 后返回。
样例 1 2 3 4 5 6 输入:nums1 = [1,7,5], nums2 = [2,3,5] 输出:3 解释:有两种可能的最优方案: - 将第二个元素替换为第一个元素:[1,7,5] => [1,1,5] ,或者 - 将第二个元素替换为第三个元素:[1,7,5] => [1,5,5] 两种方案的绝对差值和都是 |1-2 | + ( |1-3 | 或者 |5-3 |) + |5-5 | = 3
1 2 3 输入:nums1 = [2 ,4 ,6 ,8 ,10 ], nums2 = [2 ,4 ,6 ,8 ,10 ] 输出:0 解释:nums1 和 nums2 相等,所以不用替换元素。绝对差值和为 0
1 2 3 4 输入:nums1 = [1,10,4,4,2,7], nums2 = [9,3,5,1,7,4] 输出:20 解释:将第一个元素替换为第二个元素:[1,10,4,4,2,7] => [10,10,4,4,2,7] 绝对差值和为 |10-9 | + |10-3 | + |4-5 | + |4-1 | + |2-7 | + |7-4 | = 20
思路 排序 + 二分查找
时间复杂度:O(nlogn),其中 n 是数组 nums1 和 nums2 的长度。我们需要记录 nums 1 中的元素,并进行排序,时间复杂度是 O(nlogn)。计算 maxn 需要进行 n 次二分查找,每次二分查找的时间为 O(logn),因此时间复杂度也是 O(nlogn)。所以总的时间复杂度为 O(nlogn)。
空间复杂度:O(n),其中 n 是数组 nums 1和 nums 2 的长度。我们需要创建大小为 n 的辅助数组。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : static constexpr int mod = 1'000'000'007 ; int minAbsoluteSumDiff (vector<int >& nums1, vector<int >& nums2) { vector<int > rec (nums1) ; sort (rec.begin (), rec.end ()); int sum = 0 , maxn = 0 ; int n = nums1.size (); for (int i = 0 ; i < n; i++) { int diff = abs (nums1[i] - nums2[i]); sum = (sum + diff) % mod; int j = lower_bound (rec.begin (), rec.end (), nums2[i]) - rec.begin (); if (j < n) { maxn = max (maxn, diff - (rec[j] - nums2[i])); } if (j > 0 ) { maxn = max (maxn, diff - (nums2[i] - rec[j - 1 ])); } } return (sum - maxn + mod) % mod; } };
July 15 - 1846. 减小和重新排列数组后的最大元素 描述 给你一个正整数数组 arr 。请你对 arr 执行一些操作(也可以不进行任何操作),使得数组满足以下条件:
arr 中 第一个 元素必须为 1 。
任意相邻两个元素的差的绝对值 小于等于 1 ,也就是说,对于任意的 1 <= i < arr.length (数组下标从 0 开始),都满足 abs(arr[i] - arr[i - 1]) <= 1 。abs(x) 为 x 的绝对值。
你可以执行以下 2 种操作任意次:
减小 arr 中任意元素的值,使其变为一个 更小的正整数 。
重新排列 arr 中的元素,你可以以任意顺序重新排列。
请你返回执行以上操作后,在满足前文所述的条件下,arr 中可能的 最大值 。
样例 1 2 3 4 5 输入:arr = [2,2,1,2,1] 输出:2 解释: 我们可以重新排列 arr 得到 [1,2,2,2,1] ,该数组满足所有条件。 arr 中最大元素为 2 。
1 2 3 4 5 6 7 8 9 输入:arr = [100,1,1000] 输出:3 解释: 一个可行的方案如下:1. 重新排列 arr 得到 [1,100,1000] 。2. 将第二个元素减小为 2 。3. 将第三个元素减小为 3 。 现在 arr = [1,2,3] ,满足所有条件。 arr 中最大元素为 3 。
1 2 3 输入:arr = [1,2,3,4,5] 输出:5 解释:数组已经满足所有条件,最大元素为 5 。
思路 排序 + 贪心
提示 1
如果一个数组是满足要求的,那么将它的元素按照升序排序后得到的数组也是满足要求的。
提示 1 解释
假设数组中出现了元素 x 和 y,且 x<y,由于相邻元素差值的绝对值小于等于 1,那么区间 [x,y] 内的所有整数应该都出现过。
只要我们令 x 和 y 分别为数组中元素的最小值和最大值,就说明了将数组升序排序后,得到的结果是不会出现「断层」的,也就是满足要求的。
提示 2
在提示 1 的基础上,我们得到了一个单调递增的数组。那么数组中相邻两个元素,要么后者等于前者,要么后者等于前者加上 1。
我们可以先将数组进行升序排序,随后对数组进行遍历,将 arr[i] 更新为其自身与 arr[i−1]+1 中的较小值即可。
最终的答案(最大值)即为 arr 中的最后一个元素。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int maximumElementAfterDecrementingAndRearranging (vector<int >& arr) { int n = arr.size (); sort (arr.begin (),arr.end ()); arr[0 ] = 1 ; for (int i=0 ; i<n-1 ; i++){ if (abs (arr[i+1 ]-arr[i])>1 ){ arr[i+1 ] = arr[i] + 1 ; } } return arr[n-1 ]; } };
July 16 - 剑指 Offer 53 - I. 在排序数组中查找数字 I 描述 统计一个数字在排序数组中出现的次数。
样例 1 2 输入: nums = [5,7,7,8,8,10], target = 8 输出: 2
1 2 输入: nums = [5,7,7,8,8,10], target = 6 输出: 0
思路 二分查找
直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target 的下标,但这个方法的时间复杂度为 O(n),没有利用到数组升序排列的条件。
由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。
时间复杂度: O(logn) ,其中 n 为数组的长度。二分查找的时间复杂度为 O(logn),一共会执行两次,因此总时间复杂度为 O(logn)。
空间复杂度:O(1) 。只需要常数空间存放若干变量。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public : int search (vector<int >& nums, int target) { int first = lower_bound (nums.begin (), nums.end (), target) - nums.begin (); int last = upper_bound (nums.begin (), nums.end (), target) - nums.begin (); if (first >= nums.size ()){ return 0 ; } else { return last - first; } } };
July 17 - 剑指 Offer 42. 连续子数组的最大和 描述 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
样例 1 2 3 输入: nums = [-2 ,1,-3 ,4,-1 ,2,1,-5 ,4] 输出: 6 解释: 连续子数组 [4,-1 ,2,1] 的和最大,为 6。
思路 动态规划
时间复杂度:O(n),其中 n 为 nums 数组的长度。我们只需要遍历一遍数组即可求得答案。
空间复杂度:O(1)。我们只需要常数空间存放若干变量。
代码 1 2 3 4 5 6 7 8 9 10 11 12 class Solution {public : int maxSubArray (vector<int >& nums) { int pre = 0 ; int result = nums[0 ]; for (const auto &x:nums){ pre = max (x, pre+x); result = max (result, pre); } return result; } };
July 18 - 面试题 10.02. 变位词组 描述 编写一种方法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母相同,但排列不同的字符串。
样例 1 2 3 4 5 6 7 8 输入: ["eat" , "tea" , "tan" , "ate" , "nat" , "bat" ], 输出: [ ["ate" ,"eat" ,"tea" ], ["nat" ,"tan" ], ["bat" ] ] 说明:所有输入均为小写字母,不考虑答案输出的顺序。
思路 两个字符串互为变位词,当且仅当两个字符串包含的字母相同。同一组变位词中的字符串具备相同点,可以使用相同点作为一组变位词的标志,使用哈希表存储每一组变位词,哈希表的键为一组变位词的标志,哈希表的值为一组变位词列表。
遍历每个字符串,对于每个字符串,得到该字符串所在的一组变位词的标志,将当前字符串加入该组变位词的列表中。遍历全部字符串之后,哈希表中的每个键值对即为一组变位词。
由于互为变位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定是相同的,故可以将排序之后的字符串作为哈希表的键。
时间复杂度:O(nklogk),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要遍历 n 个字符串,对于每个字符串,需要 O(klogk) 的时间进行排序以及 O(1) 的时间更新哈希表,因此总时间复杂度是 O(nklogk)。
空间复杂度:O(nk),其中 n 是 strs 中的字符串的数量,k 是 strs 中的字符串的的最大长度。需要用哈希表存储全部字符串。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : vector<vector<string>> groupAnagrams (vector<string>& strs) { unordered_map<string,vector<string>> map; for (auto str:strs){ auto tmpstr = str; sort (tmpstr.begin (),tmpstr.end ()); map[tmpstr].push_back (str); } vector<vector<string>> ans; for (auto i=map.begin (); i!=map.end (); it++){ ans.push_back (it->second); } return ans; } };
July 19 - 1838. 最高频元素的频数 描述 元素的 频数 是该元素在一个数组中出现的次数。
给你一个整数数组 nums 和一个整数 k 。在一步操作中,你可以选择 nums 的一个下标,并将该下标对应元素的值增加 1 。
执行最多 k 次操作后,返回数组中最高频元素的 最大可能频数 。
样例 1 2 3 4 输入:nums = [1 ,2 ,4 ], k = 5 输出:3 解释:对第一个元素执行 3 次递增操作,对第二个元素执 2 次递增操作,此时 nums = [4 ,4 ,4 ] 。4 是数组中最高频元素,频数是 3 。
1 2 3 4 5 6 输入:nums = [1,4,8,13], k = 5 输出:2 解释:存在多种最优解决方案: - 对第一个元素执行 3 次递增操作,此时 nums = [4,4,8,13] 。4 是数组中最高频元素,频数是 2 。 - 对第二个元素执行 4 次递增操作,此时 nums = [1,8,8,13] 。8 是数组中最高频元素,频数是 2 。 - 对第三个元素执行 5 次递增操作,此时 nums = [1,4,13,13] 。13 是数组中最高频元素,频数是 2 。
1 2 输入:nums = [3 ,9 ,6 ], k = 2 输出:1
思路 排序 + 滑动窗口
提示 1
操作后的最高频元素必定可以是数组中已有的某一个元素。
提示 2
优先操作距离目标值最近的(小于目标值的)元素。
提示 3
遍历数组中的每个元素作为目标值并进行尝试。此处是否存在一些可以用于优化算法的性质?
思路与算法
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。排序数组的时间复杂度为 O(nlogn),使用滑动窗口遍历目标值的时间复杂度为 O(n)。
空间复杂度:O(logn),即为排序数组需要使用的栈空间。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution {public : int maxFrequency (vector<int >& nums, int k) { int max = 0 ; int n = nums.size (); if (n == 0 ){ return 0 ; }else if (n == 1 ){ return 1 ; }else { sort (nums.begin (),nums.end ()); for (int i = 1 ; i<nums.size (); i++){ int temp = 1 ; int tempk = k; for (int j = i-1 ; j>=0 ; j--){ tempk -= (nums[i] - nums[j]); if (tempk>=0 ){ temp++; } else { break ; } } if (temp>max){ max = temp; } } } return max; } };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int maxFrequency (vector<int >& nums, int k) { sort (nums.begin (), nums.end ()); int n = nums.size (); long long total = 0 ; int l = 0 , res = 1 ; for (int r = 1 ; r < n; ++r) { total += (long long )(nums[r] - nums[r - 1 ]) * (r - l); while (total > k) { total -= nums[r] - nums[l]; ++l; } res = max (res, r - l + 1 ); } return res; } };
July 20 - 1877. 数组中最大数对和的最小值 描述 一个数对 (a,b) 的 数对和 等于 a + b 。最大数对和 是一个数对数组中最大的 数对和 。
比方说,如果我们有数对 (1,5) ,(2,3) 和 (4,4),最大数对和 为 max(1+5, 2+3, 4+4) = max(6, 5, 8) = 8 。
给你一个长度为 偶数 n 的数组 nums ,请你将 nums 中的元素分成 n / 2 个数对,使得:
nums 中每个元素 恰好 在 一个 数对中,且
最大数对和 的值 最小 。
请你在最优数对划分的方案下,返回最小的 最大数对和 。
样例 1 2 3 4 输入:nums = [3 ,5 ,2 ,3 ] 输出:7 解释:数组中的元素可以分为数对 (3 ,3 ) 和 (5 ,2 ) 。 最大数对和为 max (3 + 3 , 5 + 2 ) = max (6 , 7 ) = 7 。
1 2 3 4 输入:nums = [3 ,5 ,4 ,2 ,4 ,6 ] 输出:8 解释:数组中的元素可以分为数对 (3 ,5 ),(4 ,4 ) 和 (6 ,2 ) 。 最大数对和为 max (3 + 5 , 4 + 4 , 6 + 2 ) = max (8 , 8 , 8 ) = 8 。
思路 排序 + 贪心
提示 1
数组内只有两个数的情况是平凡的。我们可以考虑数组中只有四个数 x1≤x2≤x3≤x4 的情况。此时 (x1,x4),(x2,x3) 的拆分方法对应的最大数对和一定是最小的。
提示 2
对于 n 个数(n 为偶数)的情况,上述的条件对应的拆分方法,即第 kk 大与第 kk 小组成的 n / 2n/2 个数对,同样可以使得最大数对和最小。
根据 提示 2,我们需要将 nums 排序。排序后,我们遍历每一个第 k 大与第 k 小组成的数对,计算它们的和,并维护这些和的最大值。同样根据 提示 2,遍历完成后求得的最大数对和就是满足要求的最小值。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int minPairSum (vector<int >& nums) { int max = 0 ; int n = nums.size (); sort (nums.begin (),nums.end ()); for (int i=0 ;i<n/2 ;i++){ if (max < nums[i]+nums[n-i-1 ]){ max = nums[i]+nums[n-i-1 ]; } } return max; } };