当前位置: 首页 > news >正文

【Algorithm】Day-2

本篇文章主要进行双指针与滑动窗口算法练习题的讲解


1  有效三角形的个数

leetcode链接:https://leetcode.cn/problems/valid-triangle-number/description/?envType=problem-list-v2&envId=two-pointers

题目描述

给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。

示例 1:

输入: nums = [2,2,3,4]
输出: 3
解释:有效的组合是: 
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

示例 2:

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

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

题目解析

        题目中会给你一个整数数组 nums,其中的元素都是非负整数,该题目要求返回 nums 中所有能够构成三角形三条边的三元组的个数。比如 nums = [2, 2, 3, 4],能够构成三角形的三元组有三个,分别是[2, 3, 4],[2, 3, 4],[2, 2, 3],虽然构成的三角形有两组是相同的,但是只要使用的是不同的元素,就算是两个不同的三元组。

算法讲解

        该题目涉及到了三角形的相关数学知识,假设三条边长度为 a, b, c,那么要想构成三角形,三条边必须满足 a + b > c,a + c > b,,b + c > a,a - b < c,c - a < b,c - b < a,即两边之和大于第三边,两边之差小于第三边。但是还有一个理论就是如果两条较小边的和大于那条最大的边,那么这三条边就可以构成三角形。比如三条边 a, b, c(a < b < c),如果 a + b > c,那么 a, b, c 就可以构成三角形。因为 c 为最大边,那么 a + c > b 、b + c > a、b - a < c 都是成立的,又 a + b > c ==> c - a < b 与 c -b < a,这样就得到了构成三角形的所有条件,所以只要 a + b > c,那么就可以构成三角形。

        基于上述理论,我们只需要找到最大边,然后再找到两条较小的边,且满足这两条较小的边的和大于最大边就可以找到一个构成三角形的三元组。为了方便寻找最大边,我们先对数组按升序进行排序,那么从最后一个元素开始向前枚举,就依次为最大的边,我们可以在前面暴力枚举出所有的两条边进行判断,如果两条边的和大于最大边,那就可以构成三角形:

class Solution 
{
public:int triangleNumber(vector<int>& nums) {//先对数组进行排序sort(nums.begin(), nums.end());//用 max 来标记最大边int max = nums.size() - 1;int count = 0;//count 记录最终结果while (max > 1){for (int i = 0; i < max - 1; i++){for (int j = i + 1; j < max; j++){if (nums[i] + nums[j] > nums[max])++count;}}--max;}return count;}
};

显然,这个算法的时间复杂度是 O(n^2) 的。

        那么我们怎么可以进行优化呢?当我们在中间选取两条边的时候并没有利用上其有序的特性,我们可以先选取 0 下标和 max - 1 下标两条边作为三角形的另外两条边,如果 nums[0] + nums[max - 1] > nums[max],那么 [0, max - 2] 的所有边都能够与 max - 1 和 max 这两条边构成三角形,因为有序性, [1, max - 2] 都会 0 大,既然 nums[0] + nums[max - 1] > nums[max],那么 nums[i] + nums[max - 1] 也必然会大于 nums[max](0 < i < max - 1),如果 nums[0] + nums[max - 1] <= nums[max],那就继续判断 nums[1] + nums[max - 1] 是否大于 nums[max],就重复以上逻辑,就可以找出当 nums.size() - 1 为最大边的时候的三角形的三元组;之后,再选取 nums.size() - 2 为最大边,一次类推,就可以得到所有的能够构成三角形的三元组的个数了。所以,这道题目的解法就是双指针解法。

代码

class Solution 
{
public:int triangleNumber(vector<int>& nums) {//先对数组进行排序sort(nums.begin(), nums.end());//用 max 来标记最大边int max = nums.size() - 1;int count = 0;//count 记录最终结果while (max > 1){int left = 0, right = max - 1;while (left < right){if (nums[left] + nums[right] > nums[max]){count += (right - left);--right;}else ++left;}--max;}return count;}
};

显然,时间复杂度是 O(n^2) 的。


2  三数之和

leetcode链接:https://leetcode.cn/problems/3sum/?envType=problem-list-v2&envId=two-pointers

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

题目解析

        该题目会给你一个整数数组 nums,其中 nums 数组中既可能有负数,也可能有正数与 0,该题目的要求就是让你找出三个和为 0 数字组合,并将所有满足该条件的三元组放到 vector<vector<int>> 中并返回该 vector<vector<int>>,要注意的是返回的所有的三元组不能有重复的三元组,这里的不重复就是指三元组的三个数字不能完全相同。例如 nums = [-2, -1, -1, 0, 0, 1, 2, 2, 3],那么返回的 vector<vector<int>> = [[-1, 0 ,1], [-2, 0, 2], [-1, -1, 2], [-2, -1, 3]]。

算法解析

        该题目的算法比较复杂,我们对题目要求进行变形,nums[i] + nums[j] + nums[k] == 0 变为 nums[i] + nums[j] == -nums[k],这样其实就很想我们之前做过的一个题目,就是在数组中寻找一个和为 target 的两个元素,所以这道题目我们可以采用先排序,如果最小的数字是正数,那就说明没有三个数字的和为0,那就直接返回空数组就可以了;如果最小的数字为负数,那就依次将最小的数字的负数作为那个目标 target,然后在 target 的后面寻找两个元素 left 与 right,如果 nums[left] + nums[right] ==  -target,那就将 nums[left]、nums[right] 与 target 一起尾插到 vector 中,这样就找到了一个三元组。如果 nums[left] + nums[right] > -target,说明是大了,由于数组已经有序了,所以直接 --right 就可以了;如果 nusm[left] + nums[right] < -target,那就 ++left。然后选取到一个满足条件的 left 与 right 之后,我们需要继续 ++left 与 --right。

        题目中还有一个条件,那就是去重。这里的去重也很简单,由于排完序之后,相同的元素都会排在一起,所以在我们判断完 nums[left] + nums[right] 是否等于 -target 之后,再判断,如果 nums[left] ==nums[left - 1],那就 ++left;如果 nums[right] == nums[right + 1],那就 --right。

代码: 

class Solution 
{
public:vector<vector<int>> threeSum(vector<int>& nums) {//先对数组进行排序sort(nums.begin(), nums.end());vector<vector<int>> v;//然后依次选取最小的元素作为目标 targetfor (int i = 0; i < nums.size() - 2; i++){//如果最小的元素大于0,那就直接退出循环if (nums[i] > 0) break;//这里也需要去重if (i > 0 && nums[i] == nums[i - 1]) continue;//右边选取两个元素,使得他们的和等于 -targetint left = i + 1, right = nums.size() - 1;int target = -nums[i];while (left < right){int sum = nums[left] + nums[right];if (sum > target) --right;else if (sum < target) ++left;else{v.push_back({nums[i], nums[left], nums[right]});++left;--right;//去重while (left < right && nums[left] == nums[left - 1]) ++left;while (left < right && nums[right] == nums[right + 1]) --right;}}}return v;}
};

3  将 x 减到 0 的最小操作数

leetcode链接:https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/description/?envType=problem-list-v2&envId=sliding-window

题目描述

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例 1:

输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。

示例 2:

输入:nums = [5,6,7,8,9], x = 4
输出:-1

示例 3:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109

题目解析

        这道题目会给你一个整数数组 nums 和一个整数 x,可以进行的操作是,从数组的最左边或者最右边选择一个数字,让 x 减去该数字,之后重复此操作直到 x 减为了 0,返回所减数字的最小次数,如果 x 不能减为 0,那就返回 -1。需要注意一点的是,题目中的需要修改数组以供接下来的操作使用并不是真正的将元素删去,而是使用过最左边或者最右边的那个数字之后,那个数字从逻辑上就相当于消失了,如果用的是最左边的元素,那么他的下一个元素就成为了最左边的元素;如果用的是最右边的元素,那么前一个元素就成为了最右边的元素。例如示例1:nums = [1, 1, 4, 2, 3],x = 5,我们可以先用 x 减去最左边的 1,此时 nums = [1, 4, 2, 3],x = 4,再减去最左边的 1,nums = [4, 2, 3],x = 3,再减去最右边的 3,nums = [4, 2],x = 0,这次的操作数为 3;当然,最少次数的操作数应该是依次减去最右边的 3,2,nums = [1, 1, 4],x = 0,操作数为2。

算法讲解

        这道题目正着比较难解,我们可以反着想。原题是要我们在左边找一段区间,右边找一段区间,使得这两个区间的和正好等于 x,求出这两个区间长度的最小值;那么我们可以反着想,不就是在中间找一段区间,使得中间的和为 sum - x(sum 为整个数组元素的和),求满足条件的区间长度的最大值,如果我们把 sum - x 看成 target,这不就转化成了在数组中找一段最长的区间,使得该区间的和为 target 吗?之前我们做过一个长度最小的子数组,这道题目不就相当于长度最大的子数组吗,所以这道题目就是采用滑动窗口算法。

        既然采用滑动窗口算法,那么就按照那四步来分析:

(1) 先求出数组的和 arr_sum,定义 left = 0, right = 0, target = arr_sum - x, len = -1, sum = 0,sum 为中间区域元素的和

(2) 进窗口:sum += nums[right],++right

(3) 判断:当 sum > target 时,出窗口,也就是 sum -= nums[left], ++left

(4) 更新结果:由于只有在 sum == target 的时候才会更新结果,所以我们需要判断一下,如果相等,那才更新结果

最后,需要注意一点,该题目最终返回的是最小操作数,当 len = -1 时,说明中间没有区域的和是等于 target 的,直接返回 -1;当 len 不等于 -1 的时候,需要返回 nums.size() - len。

代码

class Solution 
{
public:int minOperations(vector<int>& nums, int x) {//先求整个数组的和int arr_sum = 0;for (auto& e : nums)arr_sum += e;//定义变量int target = arr_sum - x;int left = 0, right = 0, sum = 0, len = -1;//由于数组中都是正整数,所以不可能加出小于0的数if (target < 0) return -1;while (right < nums.size()){//进窗口sum += nums[right];//判断while (sum > target){//出窗口sum -= nums[left++];}//更新结果if (sum == target) len = max(len, right - left + 1);++right;}return len == -1 ? -1 : nums.size() - len;}
};
http://www.dtcms.com/a/458661.html

相关文章:

  • 淘宝网站那个做的最好加盟网站建设
  • 本地的丹阳网站建设合肥大型网站
  • 做网站登录平台网站是什么
  • 个人用云计算学习笔记 --21(Linux 文本处理工具与正则表达式)
  • 室内装饰公司网站模板wordpress 主题上传
  • 网站迁移到别的服务器要怎么做人力资源公司简介
  • 第六步:加入日志功能
  • 16.C++三大重要特性之多态
  • 鄂州网站建设网站建设的界面f分
  • 网站添加对联广告代码shine跨境电商平台
  • Linux VScode 安装PHP环境
  • 云速建站与传统网站的区别在线设计装修软件
  • 南安建设局网站wordpress英文模版
  • leaflow 部署openlist 部署教程
  • 成都网站空间创新互联网站建设与管理维护的答案李建青
  • photoprism开源去中心化网络的 AI 照片应用
  • 如何在记事本中做网站链接教务处网站建设方案
  • 管理系统网站模板下载上海国家企业信用网
  • 网站怎么优化关键词南京网站设计公司哪儿济南兴田德润怎么联系
  • 博白建设局网站游戏网站logo制作
  • Linux 命令:readlink
  • 三亚建设局网站网站建设上传视频教程
  • 怎样自己建设一个网站济南建设工程信息网官网
  • 买别人做的网站能盗回吗网站建设国家有补贴吗
  • 企业网站的公司和产品信息的介绍与网络营销关系wordpress使用教程书
  • 出版社网站建设方案宁波seo优化项目
  • 自建站电商外贸网站后台fpt
  • 上海医疗 网站制作建设工程人员押证在哪个网站查
  • 吴恩达机器学习课程(PyTorch 适配)学习笔记:3.1 无监督学习基础
  • 做移动类网站的书推荐2014个人网站备案