Leetcode刷题笔记——剑指offer II (五)【二分、排序、回溯】
这里写目录标题
- 二分查找
- 剑指 Offer II 069. 山峰数组的顶部
- 剑指 Offer II 070. 排序数组中只出现一次的数字
- 剑指 Offer II 071. 按权重生成随机数
- 剑指 Offer II 072. 求平方根
- 剑指 Offer II 073. 狒狒吃香蕉
- 4. 寻找两个正序数组的中位数
- 33. 搜索旋转排序数组
- 排序
- 剑指 Offer II 074. 合并区间
- 剑指 Offer II 075. 数组相对排序
- 归并排序 (比较占用内存,但效率高且稳定)
- 剑指 Offer II 077. 链表排序
- 剑指 Offer II 078. 合并排序链表
- 剑指 Offer 51. 数组中的逆序对
- 769. 最多能完成排序的块
- 回溯(二叉树、多叉树)
- 剑指 Offer II 079. 所有子集
- 剑指 Offer II 080. 含有 k 个元素的组合
- 剑指 Offer II 081. 允许重复选择元素的组合
- 剑指 Offer II 082. 含有重复元素集合的组合
- 剑指 Offer II 083. 没有重复元素集合的==全排列==
- 剑指 Offer II 084. 含有重复元素集合的全排列
- 剑指 Offer II 085. 生成匹配的括号
- 剑指 Offer II 086. 分割回文子字符串
- 剑指 Offer II 087. 复原 IP
- (归并) 856. 括号的分数
二分查找
剑指 Offer II 069. 山峰数组的顶部
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :
arr.length >= 3
- 存在
i
(0 < i < arr.length - 1
)使得:arr[0] < arr[1] < ... arr[i-1] < arr[i]
arr[i] > arr[i+1] > ... > arr[arr.length - 1]
给定由整数组成的山峰数组arr
,返回任何满足arr[0] < arr[1] < ... arr[i - 1] < arr[i] > arr[i + 1] > ... > arr[arr.length - 1]
的下标i
,即山峰顶部。
示例 1:输入:arr = [0,1,0]
输出:1
示例 2:输入:arr = [1,3,5,4,2]
输出:2
示例 3:输入:arr = [0,10,5,2]
输出:1
示例 4:输入:arr = [3,4,5,1]
输出:2
示例 5:输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2提示:3 <= arr.length <= 104
0 <= arr[i] <= 106
题目数据保证 arr 是一个山脉数组进阶:很容易想到时间复杂度 O(n) 的解决方案,你可以设计一个 O(log(n)) 的解决方案吗?
我的方法:遍历
int peakIndexInMountainArray(vector<int>& arr) {for (int i = 1; i < arr.size(); i++) {if (arr[i] < arr[i - 1]) return i - 1;}return arr.size() - 1;
}
复杂度分析
时间复杂度:O(n),其中 n 是数组 arr\textit{arr}arr 的长度。我们最多需要对数组 arr\textit{arr}arr 进行一次遍历。
空间复杂度:O(1)。
方法二:二分查找
思路与算法
记满足题目要求的下标 i
为 iansi_\textit{ans}ians。我们可以发现:
当 i<iansi < i_\textit{ans}i<ians时,arri<arri+1\textit{arr}_i < \textit{arr}_{i+1}arri<arri+1恒成立;
当 i≥iansi \geq i_\textit{ans}i≥ians时,arri>arri+1\textit{arr}_i > \textit{arr}_{i+1}arri>arri+1 恒成立。
这与方法一的遍历过程也是一致的,因此 iansi_\textit{ans}ians即为「最小的满足 arri>arri+1\textit{arr}_i > \textit{arr}_{i+1}arri>arri+1的下标 iii」,我们可以用二分查找的方法来找出 iansi_\textit{ans}ians。
int peakIndexInMountainArray(vector<int>& arr) {int n = arr.size();int left = 1, right = n - 2, ans = 0;while (left <= right) {int mid = (left + right) / 2;if (arr[mid] > arr[mid + 1]) {ans = mid;right = mid - 1;}else {left = mid + 1;}}return ans;}
复杂度分析
- 时间复杂度:O(logn)O(\log n)O(logn),其中 nn 是数组 arr\textit{arr}arr 的长度。我们需要进行二分查找的次数为 O(logn)O(\log n)O(logn)。
- 空间复杂度:O(1)。
剑指 Offer II 070. 排序数组中只出现一次的数字
给定一个只包含整数的有序数组 nums ,每个元素都会出现两次,唯有一个数只会出现一次,请找出这个唯一的数字。
示例 1:输入: nums = [1,1,2,3,3,4,4,8,8]
输出: 2示例 2:输入: nums = [3,3,7,7,10,11,11]
输出: 10提示:1 <= nums.length <= 105
0 <= nums[i] <= 105进阶: 采用的方案可以在 O(log n) 时间复杂度和 O(1) 空间复杂度中运行吗?
我的方法:遍历异或
int singleNonDuplicate(vector<int>& nums) {int ans = nums[0];for (int i = 1; i < nums.size(); i++) {ans ^= nums[i];}return ans;
}
复杂度:O(n), O(1)
方法二:全数组的二分查找
假设只出现一次的元素位于下标 x
,由于其余每个元素都出现两次,因此下标 x
的左边和右边都有偶数个元素,数组的长度是奇数。
由于数组是有序的,因此数组中相同的元素一定相邻。对于下标 x
左边的下标 y
,如果 nums[y]=nums[y+1]\textit{nums}[y] = \textit{nums}[y + 1]nums[y]=nums[y+1],则 yyy 一定是偶数;对于下标 xxx 右边的下标 zzz,如果 nums[z]=nums[z+1]\textit{nums}[z] = \textit{nums}[z + 1]nums[z]=nums[z+1],则 zzz 一定是奇数。由于下标 xxx 是相同元素的开始下标的奇偶性的分界,因此可以使用二分查找的方法寻找下标 xxx。
初始时,二分查找的左边界是 000,右边界是数组的最大下标。每次取左右边界的平均值 mid\textit{mid}mid 作为待判断的下标,根据 mid\textit{mid}mid 的奇偶性决定和左边或右边的相邻元素比较:
- 如果 mid\textit{mid}mid 是偶数,则比较 nums[mid]\textit{nums}[\textit{mid}]nums[mid] 和 nums[mid+1]\textit{nums}[\textit{mid} + 1]nums[mid+1] 是否相等;
- 如果 mid\textit{mid}mid是奇数,则比较 nums[mid−1]\textit{nums}[\textit{mid} - 1]nums[mid−1] 和 nums[mid]\textit{nums}[\textit{mid}]nums[mid] 是否相等。
如果上述比较相邻元素的结果是相等,则 mid<x\textit{mid} < xmid<x,调整左边界,否则 mid≥x\textit{mid} \ge xmid≥x,调整右边界。调整边界之后继续二分查找,直到确定下标 xxx 的值。
得到下标 xxx 的值之后,nums[x]\textit{nums}[x]nums[x] 即为只出现一次的元素。
细节
利用按位异或的性质,可以得到 mid\textit{mid}mid 和相邻的数之间的如下关系,其中 ⊕\oplus⊕ 是按位异或运算符:
- 当 mid\textit{mid}mid是偶数时,mid+1=mid⊕1\textit{mid} + 1 = \textit{mid} \oplus 1mid+1=mid⊕1;
- 当 mid\textit{mid}mid是奇数时,mid−1=mid⊕1\textit{mid} - 1 = \textit{mid} \oplus 1mid−1=mid⊕1。
因此在二分查找的过程中,不需要判断 mid\textit{mid}mid的奇偶性,mid\textit{mid}mid和 mid⊕1\textit{mid} \oplus 1mid⊕1 即为每次需要比较元素的两个下标。
int singleNonDuplicate(vector<int>& nums) {int low = 0, high = nums.size() - 1;while (low < high) {int mid = (high - low) / 2 + low;if (nums[mid] == nums[mid ^ 1]) {low = mid + 1;} else {high = mid;}}return nums[low];
}
复杂度分析
- 时间复杂度:O(logn)O(\log n)O(logn),其中 n 是数组 nums\textit{nums}nums 的长度。需要在全数组范围内二分查找,二分查找的时间复杂度是 O(logn)O(\log n)O(logn)。
- 空间复杂度:O(1)O(1)O(1)。
剑指 Offer II 071. 按权重生成随机数
给定一个正整数数组 w
,其中 w[i]
代表下标 i 的权重(下标从 0 开始),请写一个函数 pickIndex
,它可以随机地获取下标 i
,选取下标 i
的概率与 w[i]
成正比。
例如,对于 w = [1, 3]
,挑选下标 0 的概率为 1 / (1 + 3) = 0.25
(即,25%),而选取下标 1 的概率为 3 / (1 + 3) = 0.75
(即,75%)。
也就是说,选取下标i
的概率为 w[i]/sum(w)w[i] / sum(w)w[i]/sum(w) 。
示例 1:输入:
inputs = ["Solution","pickIndex"]
inputs = [[[1]],[]]
输出:
[null,0]
解释:
Solution solution = new Solution([1]);
solution.pickIndex(); // 返回 0,因为数组中只有一个元素,所以唯一的选择是返回下标 0。
示例 2:输入:
inputs = ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"]
inputs = [[[1,3]],[],[],[],[],[]]
输出:
[null,1,1,1,1,0]
解释:
Solution solution = new Solution([1, 3]);
solution.pickIndex(); // 返回 1,返回下标 1,返回该下标概率为 3/4 。
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 1
solution.pickIndex(); // 返回 0,返回下标 0,返回该下标概率为 1/4 。由于这是一个随机问题,允许多个答案,因此下列输出都可以被认为是正确的:
[null,1,1,1,1,0]
[null,1,1,1,1,1]
[null,1,1,1,0,0]
[null,1,1,1,0,1]
[null,1,0,1,0,0]
......
诸若此类。提示:1 <= w.length <= 10000
1 <= w[i] <= 10^5
pickIndex 将被调用不超过 10000 次
我的方法:前缀和+二分
注意:
- 使用rand函数时,要先设定随机数
srand((unsigned)time(0));
才能起作用 - rand()如果想要返回double型o-1的小数,则
double ans = rand() % double(RAND_MAX);
vector<int> m;Solution(vector<int>& w) {m = vector<int>(w.size() + 1);for (int i = 1; i <= w.size(); i++)m[i] = m[i - 1] + w[i - 1];srand((unsigned)time(0));}int pickIndex() {int n = rand() % m.back() + 1;auto it = lower_bound(m.begin(), m.end(), n);return it-m.begin()-1;}
剑指 Offer II 072. 求平方根
给定一个非负整数 x ,计算并返回 x 的平方根,即实现 int sqrt(int x) 函数。
正数的平方根有两个,只输出其中的正数平方根。
如果平方根不是整数,输出只保留整数的部分,小数部分将被舍去。
示例 1:输入: x = 4
输出: 2
示例 2:输入: x = 8
输出: 2
解释: 8 的平方根是 2.82842...,由于小数部分将被舍去,所以返回 2提示:0 <= x <= 231 - 1
我的方法:二分:
注意事项,此处不能用m*m
作为判断条件,因为可能会整数越界
如果用m*m,则必须用long long
数据类型
int mySqrt(int x) {if (x<2) return x;int l=1, r=x, m;while (l < r) {m = l + (r - l) / 2;if (m == x / m) return m;else if (m > x / m) r = m;else l = m + 1;}return l - 1;
}
复杂度分析
时间复杂度:O(logx)O(\log x)O(logx),即为二分查找需要的次数。
空间复杂度:O(1) 。
进阶:牛顿法
剑指 Offer II 073. 狒狒吃香蕉
狒狒喜欢吃香蕉。这里有 n 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 h 小时后回来。
狒狒可以决定她吃香蕉的速度 k (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 k 根。如果这堆香蕉少于 k 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉,下一个小时才会开始吃另一堆的香蕉。
狒狒喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 h 小时内吃掉所有香蕉的最小速度 k(k 为整数)。
示例 1:输入:piles = [3,6,7,11], h = 8
输出:4
示例 2:输入:piles = [30,11,23,4,20], h = 5
输出:30
示例 3:输入:piles = [30,11,23,4,20], h = 6
输出:23提示:1 <= piles.length <= 104
piles.length <= h <= 109
1 <= piles[i] <= 109
我的方法:二分
多联想!
对于本题,m的取值范围实际上可以确定下来,即1<=m<=max_element(piles)
因此,我们可以通过遍历m来得到最终答案,这个过程可以用二分搜索来加速运算
int helperMinEatingSpeed(vector<int> piles, int m, int h) {for (auto& i : piles) {int tmp = i/m;h = (i % m!=0)? h - tmp - 1 : h-tmp;}return h;
}
int minEatingSpeed(vector<int>& piles, int h) {int l = 1, r = *(max_element(piles.begin(), piles.end())) + 1, m;while (l < r) {m = l + (r - l) / 2;int tmp = helperMinEatingSpeed(piles, m, h);if (tmp == 0) r = m;else if (tmp < 0) {l = m + 1;}else r = m;}return l;
}
复杂度分析
时间复杂度:O(NlogW)O(N \log W)O(NlogW),其中 N 是香蕉堆的数量, W 最大的香蕉堆的大小。
空间复杂度:O(1)O(1)O(1)。
4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5提示:nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
思路:二分,舍弃折半区间
要在两个有序数组中,找中位数。
假设两个数组长度大小分别为 m, n,则原问题等价为:
- 如果m+n为奇数,则在两个有序数组中,寻找第 (m+n)//2(m+n)//2(m+n)//2 的数
- 如果m+n为偶数,则在两个有序数组中,寻找第 (m+n)//2(m+n)//2(m+n)//2 与 (m+n)//2+1(m+n)//2+1(m+n)//2+1 的数
我们定义一个函数 int getKthElement(int k)
以实现上述功能:
每次划定两个数组中,前 k//2 个值 [idx, idx+k//2-1]
,比较两个区间终点 idx+k//2-1
的值的大小,每次都能够舍弃小的数组的划定的区间。
class Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:def getKthElement(k: int)-> int:idx1, idx2 = 0, 0while True:if idx1==m:return nums2[idx2+k-1] # 返回区间终点if idx2==n:return nums1[idx1+k-1]if k==1:return min(nums1[idx1], nums2[idx2])newIdx1 = min(idx1+k//2-1, m-1)newIdx2 = min(idx2+k//2-1, n-1)pivot1, pivot2 = nums1[newIdx1], nums2[newIdx2]if pivot1<=pivot2:k -= newIdx1-idx1+1idx1 = newIdx1+1 # 更新,idx = 区间终点+1else:k -= newIdx2-idx2+1idx2 = newIdx2+1m, n = len(nums1), len(nums2)totalLen = m+nif totalLen%2 == 1:return getKthElement((totalLen+1)//2)else:return (getKthElement(totalLen//2) + getKthElement(totalLen//2+1))/2
复杂度分析
- 时间复杂度:O(log(m+n))O(\log(m+n))O(log(m+n)),其中 mmm 和 nnn 分别是数组 nums1\textit{nums}_1nums1 和 nums2\textit{nums}_2nums2 的长度。初始时有 k=(m+n)/2k=(m+n)/2k=(m+n)/2 或 k=(m+n)/2+1k=(m+n)/2+1k=(m+n)/2+1,每一轮循环可以将查找范围减少一半,因此时间复杂度是 O(log(m+n))O(\log(m+n))O(log(m+n))。
- 空间复杂度:O(1)O(1)O(1)。
33. 搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。
示例 1:输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:输入:nums = [1], target = 0
输出:-1
思路:二分,只搜索有序的部分
class Solution:def search(self, nums: List[int], target: int) -> int:l, r = 0, len(nums)-1while l<=r:m = l + (r-l)//2if nums[m]==target:return melif nums[m]>=nums[0]: #
Leetcode刷题笔记——剑指offer II (五)【二分、排序、回溯】相关推荐
- Leetcode刷题笔记——剑指offer II (六)【图】
文章目录 图 695. 岛屿的最大面积 剑指 Offer II 106. 二分图 (每条边的两个结点都来自不同的集合) 剑指 Offer II 107. 矩阵中的距离 (最短路问题,建图,BFS) 剑 ...
- Leetcode刷题笔记——剑指offer II (一)【整数、数组、字符串、链表】
目录 整数 剑指 Offer II 001. 整数除法 剑指 Offer II 002. 二进制加法 剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 剑指 Offer II 00 ...
- leetcode刷题笔记——剑指offer(二)[回溯、排序、位运算、数学、字符串]
这里写目录标题 搜索与回溯 剑指 Offer 12. 矩阵中的路径 剑指 Offer 13. 机器人的运动范围 剑指 Offer 34. 二叉树中和为某一值的路径 剑指 Offer 36. 二叉搜索树 ...
- Leetcode刷题笔记——剑指 Offer 46. 把数字翻译成字符串(中等)
文章目录 题目描述 解法:动态规划 方法一:字符串遍历 复杂度分析 C++代码实现 方法二:数字求余 复杂度分析 C++代码实现 参考链接 题目描述 给定一个数字,我们按照如下规则把它翻译为字符串:0 ...
- Leetcode刷题笔记——剑指 Offer 13. 机器人的运动范围(中等)
文章目录 题目描述 解题思路 方法一:深度优先遍历DFS 复杂度分析 C++代码实现 方法二:广度优先遍历BFS 复杂度分析 C++代码实现 参考连接 题目描述 地上有一个m行n列的方格,从坐标 [0 ...
- 【刷题】剑指Offer篇
本文中的部分图片摘自相关题解榜主,如有侵权,请联系删除. 特别感谢k神在剑指Offer刷题路上提供的清晰图解.和堪称完美的思路与方法 小文目录: T3:数组中重复的数字 T3-2:不修改数组找出重复的 ...
- leetcode刷题之 剑指offe 面试题05. 替换空格 犯傻记录
2020.3.6 刷到剑指offer第5题,题目描述如下: 题目本身没啥好说的,就是先统计空格的个数,然后把长度增加,用两个指针从后往前插. 采用m = s.count(' ',0,len(s)-1) ...
- 力扣刷题笔记——剑指offer100题
位运算 Java位运算符:Java移位运算符.复合位赋值运算符及位逻辑运算符 1. 整数除法 题目描述: 给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 '*'.除号 '/' ...
- 如何判断链表中是否存在环?Floyd判圈算法 leetcode刷题笔记 142. 环形链表 II
这道题使用了floyd判圈算法,所以先讲解floyd算法的原理和实现,最后在附加上第142题的代码. floyd算法: 一.用途: 可以在有限状态机.迭代函数或者链表上判断是否存在环,求出该环的起点与 ...
最新文章
- Oracle中 的 DBMS_CRYPTO加密包
- Linux小工具(3)之/proc目录详细介绍(上)
- Android工程模块化平台的设计
- 编写Qt Designer自定义控件(一)——如何创建并使用Qt自定义控件
- 市场分享竞品分析-Android
- Leetcode: Median of Two Sorted Arrays
- mysql排序规则错误_MySQL中“非法混合排序规则”错误的疑难解答
- 基于源码剖析nodejs模块系统
- 设计模式系列——三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)...
- 第一次用AX2009正式版!
- 内核中自带的内存调试方法CONFIG_DEBUG_SLAB
- 2021-05-22下载ts m3u8视频方法
- tegra3 CPU auto hotplug和Big/little switch工作的基本原理
- Linux 访问superio 寄存器,ITE Super IO 学习 - GPIO
- OPCUA 复杂服务器对应的Python客户端(回调与方法)
- 一个月裂变50多万人?商城引流模式玩法——分享购
- 微信小程序版的登录注册
- 京东快递开接外单偏平台商户
- latex+bibtex+jabref
- 【python】Dpark源码分析
热门文章