这里写目录标题

  • 二分查找
    • 剑指 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
  • 存在 i0 < 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(log⁡n)O(\log n)O(logn),其中 nn 是数组 arr\textit{arr}arr 的长度。我们需要进行二分查找的次数为 O(log⁡n)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(log⁡n)O(\log n)O(logn),其中 n 是数组 nums\textit{nums}nums 的长度。需要在全数组范围内二分查找,二分查找的时间复杂度是 O(log⁡n)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(log⁡x)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(Nlog⁡W)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 (五)【二分、排序、回溯】相关推荐

  1. Leetcode刷题笔记——剑指offer II (六)【图】

    文章目录 图 695. 岛屿的最大面积 剑指 Offer II 106. 二分图 (每条边的两个结点都来自不同的集合) 剑指 Offer II 107. 矩阵中的距离 (最短路问题,建图,BFS) 剑 ...

  2. Leetcode刷题笔记——剑指offer II (一)【整数、数组、字符串、链表】

    目录 整数 剑指 Offer II 001. 整数除法 剑指 Offer II 002. 二进制加法 剑指 Offer II 003. 前 n 个数字二进制中 1 的个数 剑指 Offer II 00 ...

  3. leetcode刷题笔记——剑指offer(二)[回溯、排序、位运算、数学、字符串]

    这里写目录标题 搜索与回溯 剑指 Offer 12. 矩阵中的路径 剑指 Offer 13. 机器人的运动范围 剑指 Offer 34. 二叉树中和为某一值的路径 剑指 Offer 36. 二叉搜索树 ...

  4. Leetcode刷题笔记——剑指 Offer 46. 把数字翻译成字符串(中等)

    文章目录 题目描述 解法:动态规划 方法一:字符串遍历 复杂度分析 C++代码实现 方法二:数字求余 复杂度分析 C++代码实现 参考链接 题目描述 给定一个数字,我们按照如下规则把它翻译为字符串:0 ...

  5. Leetcode刷题笔记——剑指 Offer 13. 机器人的运动范围(中等)

    文章目录 题目描述 解题思路 方法一:深度优先遍历DFS 复杂度分析 C++代码实现 方法二:广度优先遍历BFS 复杂度分析 C++代码实现 参考连接 题目描述 地上有一个m行n列的方格,从坐标 [0 ...

  6. 【刷题】剑指Offer篇

    本文中的部分图片摘自相关题解榜主,如有侵权,请联系删除. 特别感谢k神在剑指Offer刷题路上提供的清晰图解.和堪称完美的思路与方法 小文目录: T3:数组中重复的数字 T3-2:不修改数组找出重复的 ...

  7. leetcode刷题之 剑指offe 面试题05. 替换空格 犯傻记录

    2020.3.6 刷到剑指offer第5题,题目描述如下: 题目本身没啥好说的,就是先统计空格的个数,然后把长度增加,用两个指针从后往前插. 采用m = s.count(' ',0,len(s)-1) ...

  8. 力扣刷题笔记——剑指offer100题

    位运算 Java位运算符:Java移位运算符.复合位赋值运算符及位逻辑运算符 1. 整数除法 题目描述: 给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 '*'.除号 '/' ...

  9. 如何判断链表中是否存在环?Floyd判圈算法 leetcode刷题笔记 142. 环形链表 II

    这道题使用了floyd判圈算法,所以先讲解floyd算法的原理和实现,最后在附加上第142题的代码. floyd算法: 一.用途: 可以在有限状态机.迭代函数或者链表上判断是否存在环,求出该环的起点与 ...

最新文章

  1. Oracle中 的 DBMS_CRYPTO加密包
  2. Linux小工具(3)之/proc目录详细介绍(上)
  3. Android工程模块化平台的设计
  4. 编写Qt Designer自定义控件(一)——如何创建并使用Qt自定义控件
  5. 市场分享竞品分析-Android
  6. Leetcode: Median of Two Sorted Arrays
  7. mysql排序规则错误_MySQL中“非法混合排序规则”错误的疑难解答
  8. 基于源码剖析nodejs模块系统
  9. 设计模式系列——三个工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)...
  10. 第一次用AX2009正式版!
  11. 内核中自带的内存调试方法CONFIG_DEBUG_SLAB
  12. 2021-05-22下载ts m3u8视频方法
  13. tegra3 CPU auto hotplug和Big/little switch工作的基本原理
  14. Linux 访问superio 寄存器,ITE Super IO 学习 - GPIO
  15. OPCUA 复杂服务器对应的Python客户端(回调与方法)
  16. 一个月裂变50多万人?商城引流模式玩法——分享购
  17. 微信小程序版的登录注册
  18. 京东快递开接外单偏平台商户
  19. latex+bibtex+jabref
  20. 【python】Dpark源码分析

热门文章

  1. 信息化 + 个性化再造学习生命力
  2. 教官保护手法基础要点
  3. 容器编排之战(三)连载
  4. 诺贝尔奖也难以衡量其贡献,杨振宁的杨-米尔斯理论到底多厉害?
  5. 基于CNN的图像增强之去模糊
  6. MongDB基础学习(一)
  7. 最新IOS7 越狱开发环境搭建
  8. C++学习之Socket
  9. matlab使用kirsch算子进行分割,matlab的图像分割算法
  10. STM32:利用PWM波控制飞盈电调过程和注意事项