Leetcode刷题笔记——单调性
单调性
单调性是数学中使用的一种常见性质,通常用于描述函数,在高等数学中的定义常常为:
设函数f(x)在区间I上有定义,如果对于I上的任意两个数x1和x2,当x1<x2时,有f(x1)<f(x2)(或者f(x1)>f(x2)),则称函数f(x)在区间I上是单调递增的(或者单调递减的)。
例如如下图像就是两个单调函数。

利用单调性我们可以减少很多重复的运算。例如,对于如下函数,我们给定其定义域为[0,+∞),现在要求查找出在其定义域内所有f(x)即y大于0.5的区间。

- 如果不借助单调性,我们需要采用遍历的方法,依次遍历定义域中的所有点x,判断其f(x)是否满足条件(大于0.5)。
- 如果借助单调性,我们知道上述函数是严格单调递增的,其图像如下:

绿线表示y=0.5的图像,处理该问题,只需要找到方程0.5=(1/3)x^3的解x0,由于函数具有单调性,且单调递增,因此,所有大于x0的区间内的x其f(x)都满足大于0.5。
对于计算机语言来说,用于表示函数的常见数据结构就是数组,我们可以通过
- 原数组本身的单调性
- 构造单调性
简化许多运算。下面引入几个例子:
15. 三数之和
题目:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:
- 答案中不可以包含重复的三元组
- 3 <= nums.length <= 3000
- -10^5 <= nums[i] <= 10^5
按照最朴素的解决方法,三层循环,循环遍历整个数组,然后再对整个结果进行去重,便可以解决该问题,但是时间复杂度为O(n^3),由于过于简单,这里给出伪代码:
list = [][]int{}
for i:=0;i <= len(nums)-3;i ++ {
for j:=i+1;j <= len(nums)-2;j ++ {
for k:= j+1;k < len(nums)-1;k ++ {
if nums[i]+nums[j]+nums[k] == 0 {
list = append(list, []int{nums[i],nums[j],nums[k]})
}
}
}
}
对list去重
本题目首先要求我们去重,因为返回结果要求不重复,对于去重常见的做法:
- 使用数据结构set、map进行处理,但是会额外占用内存
- 对原始数据排序,然后按序处理跳过重复项
优化掉去重问题后,我们可以尝试对内层的两层for循环进行优化,这里就引入了一个经典的方法:构造单调性,根据单调性进行查找。
巧妙的方法
如果nums[i]确定,那么我们只需要寻找满足条件nums[j]+nums[k]=-nums[i]的j、k值,这就变成了一个二数之和的问题,暴力算法是直接进行遍历,然后查找该值,但是由于数组的有序性,我们有一种更加巧妙的方法:
- 由于当前数组的有序性,保证了数组本身是单调递增(或递减的,这里以递增为例)
- 设置指针p1、p2指向数组开头
p1=i+1和结尾p2=len(nums)-1 - pred=nums[p1]+nums[p2],target=-nums[i]
- if target < pred,由于数组递增,nums[p2-1]<num[p2],因此,p2 --
- if target > pred,由于数组递增,nums[p1+1]>num[p1],因此,p1 ++
- if target == pred,找到了目标,但为防止遗漏数据还要继续查找,此时指针向任意方向移动都没有影响,可以p1 ++或者p2 --
- 直到p1 >p2则可以停止查找(=取决于需求,如果有需求可以>=或<=)
这个模式可以应用于很多地方,实际上具有单调性的函数一般都可以通过该办法查找,例如nums[j]*nums[k]=target,查找j、k。
例如,在[-1,0,1,2,-1, 3]这个数组中,查找nums[j]+nums[k]=4的nums[j]和nums[k]的值,现对其进行排序,然后用上述方法进行处理:

了解了这个模式后,我们给出解决该问题的代码:
解题代码以及注释
import "sort"
func threeSum(nums []int) [][]int {
result := [][]int{}
sort.Ints(nums)
// 尝试固定i,然后将3数之和转化为两数之和
for i := 0; i < len(nums)-2; i++ {
// 对nums[i]进行去重
if i-1 >= 0 && nums[i-1] == nums[i] {
continue
}
sum := -nums[i]
left := i + 1
right := len(nums) - 1
// 解决两数之和问题,寻找left、right使得nums[left]+nums[right]==sum
for left < right {
temp := nums[left] + nums[right]
if temp == sum {
result = append(result, []int{nums[i], nums[left], nums[right]})
// 去重nums[left]
for left < right && nums[left] == nums[left+1] {
left++
}
// 去重nums[right]
for left < right && nums[right] == nums[right-1] {
right--
}
left++
right--
} else if temp > sum {
right--
} else {
left++
}
}
}
return result
}
823. 带因子的二叉树
题目:给出一个含有不重复整数元素的数组arr,每个整数arr[i]均大于 1。用这些整数来构建二叉树,每个整数可以使用任意次数。其中:每个非叶结点的值应等于它的两个子结点的值的乘积。满足条件的二叉树一共有多少个?答案可能很大,返回 对 10^9+7 取余 的结果。
例如:输入: arr = [2, 4, 5, 10]
输出: 7
解释: 可以得到这些二叉树: [2], [4], [5], [10], [4, 2, 2], [10, 2, 5], [10, 5, 2]
该问题是一个树相关的问题,并且对于父子结点处理过程是类似的。举例说明这件事:
对于输入arr=[18, 3, 6, 2],页结点可以为[2][3][6][18],可以把未显示的结点看做空结点,对于顶点为6的树可以为[6,2,3]或者[6,3,2],就需要借助叶节点信息。对于顶点为18的树可以为[18,3,6],[18,6,3],而组成以[6]的顶点的组合有3个。可以看到该问题是个动态规划问题。
f(18)=f(3)*f(6)
f(18)=f(6)*f(3)
f(6)=f(3)*f(2)
f(6)=f(2)*f(3)
f(3)=1
f(2)=1
状态转换方程为:
\(f(a*b)= \begin{array}{ll}
f(a)*f(b)*2+1 & a!=b,a为左子树b为右子树,和a为右子树b为左子树\\
f(a)*f(b)+1, & a==b\\
\end{array}\)
那最后的问题就是查找在index属于[0,i-1]的数组中,哪些a,b满足arr[a]*arr[b]==arr[i],我们就可以使用上面提到的巧妙的方法类比解决该问题。这里就不再赘述。
解题代码和注释
func numFactoredBinaryTrees(arr []int) int {
sort.Ints(arr)
dp := make([]int64, len(arr))
res, mod := int64(0), int64(1e9 + 7)
for i := 0; i < len(arr); i++ {
dp[i] = 1
// 查找arr[left]*arr[right]==arr[right]*arr[left]
for left, right := 0, i - 1; left <= right; left++ {
for left <= right && int64(arr[left]) * int64(arr[right]) > int64(arr[i]) {
right--
}
if left <= right && int64(arr[left]) * int64(arr[right]) == int64(arr[i]) {
if left == right {
dp[i] = (dp[i] + dp[left] * dp[right]) % mod
} else {
dp[i] = (dp[i] + dp[left] * dp[right] * 2) % mod
}
}
}
res = (res + dp[i]) % mod
}
return int(res)
}
Leetcode刷题笔记——单调性的更多相关文章
- LeetCode刷题笔记和想法(C++)
主要用于记录在LeetCode刷题的过程中学习到的一些思想和自己的想法,希望通过leetcode提升自己的编程素养 :p 高效leetcode刷题小诀窍(这只是目前对我自己而言的小方法,之后会根据自己 ...
- 18.9.10 LeetCode刷题笔记
本人算法还是比较菜的,因此大部分在刷基础题,高手勿喷 选择Python进行刷题,因为坑少,所以不太想用CPP: 1.买股票的最佳时期2 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. ...
- LeetCode刷题笔记 - 12. 整数转罗马数字
学好算法很重要,然后要学好算法,大量的练习是必不可少的,LeetCode是我经常去的一个刷题网站,上面的题目非常详细,各个标签的题目都有,可以整体练习,本公众号后续会带大家做一做上面的算法题. 官方链 ...
- LeetCode刷题笔记 - 2022
这篇博客集中整理在LeetCode的刷题记录,方便查阅 258. 各位相加 - 力扣(LeetCode) (leetcode-cn.com) 代码 class Solution { public: i ...
- Leetcode刷题笔记(双指针)
1.何为双指针 双指针主要用来遍历数组,两个指针指向不同的元素,从而协同完成任务.我们也可以类比这个概念,推广到多个数组的多个指针. 若两个指针指向同一数组,遍历方向相同且不会相交,可以称之为滑动窗口 ...
- LeetCode刷题笔记(1-9)
LeetCode1-9 本文更多是作为一个习题笔记,没有太多讲解 1.两数之和 题目请点击链接 ↑ 最先想到暴力解法,直接双循环,但是这样复杂度为n平方 public int[] twoSum(int ...
- leetcode刷题笔记
(1)Best Time to Buy and Sell Stock Total Accepted: 10430 Total Submissions: 33800My Submissions Say ...
- leetcode刷题笔记08 字符串转整数 (atoi)
题目描述 实现 atoi,将字符串转为整数. 在找到第一个非空字符之前,需要移除掉字符串中的空格字符.如果第一个非空字符是正号或负号,选取该符号,并将其与后面尽可能多的连续的数字组合起来,这部分字符即 ...
- LeetCode刷题笔记-回溯法-分割回文串
题目描述: 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串. 返回 s 所有可能的分割方案. 示例: 输入: "aab"输出:[ ["aa", ...
- leetcode刷题笔记231 2的幂
题目描述: 给定一个整数,写一个函数来判断它是否是2的幂. 题目分析: 判断一个整数是不是2的幂,可根据二进制来分析.2的幂如2,4,8,等有一个特点: 二进制数首位为1,其他位为0,如2为10,4为 ...
随机推荐
- import MySQLdb as Database ModuleNotFoundError: No module named ‘MySQLdb‘
import MySQLdb as Database ModuleNotFoundError: No module named 'MySQLdb' import MySQLdb as Database ...
- .NET周报 【5月第2期 2023-05-14】
国内文章 XUnit数据共享与并行测试 https://www.cnblogs.com/podolski/p/17388602.html 在单元或者集成测试的过程中,需要测试的用例非常多,如果测试是一 ...
- 【Java】JTable的数据刷新
前言 这段时间在写一个大实验,水果超市管理系统,yes,我觉得挺大的,但是就当成了一个实验,接下来还有一个课程设计和一个实训,more bigger... 问题 在我把其他的都写好的时候去写UI层,发 ...
- 商业智能 (BI) 对企业中每个员工的 5 大好处
本文由葡萄城技术团队于博客园原创并首发.葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 众所周知,商业智能 (BI) 是探索企业数据价值的强大工具,能够帮助企业做出明智的决策.提高绩效 ...
- SpringBoot定义优雅全局统一Restful API 响应框架五
闲话不多说,继续优化 全局统一Restful API 响应框架 做到项目通用 接口可扩展. 如果没有看前面几篇文章请先看前面几篇 SpringBoot定义优雅全局统一Restful API 响应框架 ...
- 「AntV」L7地理可视化:从入门到实践
1. 前言 这是一篇由浅入深的AntV L7的学习笔记总结,记述了从了解到使用的一些过程 本文所使用的数据(包括数据处理过程)和代码均有详细描述,所有案例均可复现,甚至大部分代码可直接使用 如果喜欢分 ...
- 对象存储?CRUD Boy实现对文件的增删改查
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教. 以下是正文! 对象存储是什么? 对象存储是一种数据 ...
- 国产MCU兆易GD32实现矩阵按键扫描
一.矩阵键盘 为了减少I/O口的占用,通常将按键排列成矩阵形式.在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接.使用8个io口来进行16个按键的控制读取,可以减 ...
- 代理详解(java代理和CGLIB动态代理)
[代理]大家都知道,特别是在spring中aop.spring中的事务.spring解析注解@Configuration,以及最原始的解析spring.xml的配置,这些都是使用代理来进行实现的, ...
- Java版人脸跟踪三部曲之一:极速体验
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<Java版人脸跟踪三部曲>系列 -& ...