Author       :  叨陪鲤

Email         : vip_13031075266@163.com

Date          : 2021.01.23

Copyright : 未经同意不得转载!!!

Version    : 第一章 二分法

Reference:《LeetCode刷题笔记之模板整理》


目录

1. 二分法

1.1 什么是二分查找

1.2 如何识别二分法

1.3 二分法模板

1.3.1 模板一

1.3.2 Lc69:x的平方根

1.3.3 Lc374:猜数大小

1.3.4 Lc33:搜索旋转数组

1.3.5 模板二

1.3.6 Lc278:第一个错误版本

1.3.7 Lc162:寻找峰值

1.3.8 Lc153:寻找旋转排序数组最小值

1.3.9 Lc154:寻找旋转排序数组最小值II

1.3.10  模板三

1.3.11 LC-34:在排序数组中查找元素的第一个和最后一个

1.3.12 LC-658:找到K个最接近的元素

1.4 小结


1. 二分法

1.1 什么是二分查找

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法,前提是数据结构必须先排好序,可以在数据规模的对数时间复杂度内完成查找。但是,二分查找要求线性表具有有随机访问的特点(例如数组),也要求线性表能够根据中间元素的特点推测它两侧元素的性质,以达到缩减问题规模的效果

二分查找是计算机科学中最基本、最有用的算法之一。 它描述了在有序集合中搜索特定值的过程

二分查找中的经常使用的术语:

  • 目标 Target:你要查找的值
  • 索引 Index:你要查找的当前位置
  • 左右指示符 Left,Right:用来确定查询空间范围的指标
  • 中间指示符 Mid:用来确定接下来在左侧查找还是在右侧查找

1.2 如何识别二分法

二分查找是一种在每次查询比较之后,将查找空间一分为二的算法,查询时间复杂度通常为O(log2n)。相对于一般遍历算法性能高出很多,因此每次需要查找集合中的索引或者元素时,都应该考虑二分查找。如果集合原本是无序的,我们需要先进行排序然后再二分查找。

二分查找一般有三个主要部分组成:

  • 预处理

如果待查找集合是未排序的,首先需要对其进行排序

  • 二分查找

用循环或者递归在每一次比较之后,将查询空间一分为二

  • 后处理

在剩余空间中确定可行的候选者

1.3 二分法模板

当我们第一次学会二分查找时,我们可能会挣扎。我们可能会在网上研究数百个二分查找问题,每次我们查看开发人员的代码时,它的实现似乎都略有不同。尽管每个实现在每个步骤中都会将问题空间划分为原来的 1/2,但其中有许多问题:

  • 为什么执行方式略有不同?
  • 开发人员在想什么?
  • 哪种方法更容易?
  • 哪种方法更好?

经过许多次失败的尝试并拉扯掉大量的头发后,我们找到了三个主要的二分查找模板。为了防止脱发,并使新的开发人员更容易学习和理解,我们在接下来一一介绍他们。

1.3.1 模板一

func binarySearch(nums []int, target int) int {
if nums == nil {
return -1
} left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)>>1
if nums[mid] == target {
return mid
} else if nums[mid] < target {
left = mid + 1
} else if nums[mid] > target {
right = mid - 1
}
}
//End Condition: left > right
return -1
}

此模板是二分查找最基础和最基本的形式,是一个标准的二分查找模板,要求背诵全文。

这里说明下mid的计算:

通常情况下mid = (left + right)/2。但是这存在溢出的可能性:

比如说left,right,mid都是uint8(一个字节),

left=100,

right=250,

left + right= 350

我们想要的是(left+ right)/2 = 175,但由于350这已经超过uint8的范围了,最后算出来的结果为47,溢出的情况下算出来的值是不准确。而使用下面的方法则不存在此问题,始终可以保证left <= mid <= right。

除此之外由于移位运算的性能高于乘除运算,因此这里我采用了右移来代替除以2的操作。

mid := left + (right-left)>>1 
  • 二分查找最基础和最基本的形式
  • 查找添加可以在不与元素两侧进行比较的情况下确定
  • 无需后处理过程,因为在每一步中都在检查是否找到元素。因此如果程序到达末尾,则说明未找到该元素。
  • 初始条件: ```left=0, right=length-1```
  • 终止    : ```left > right```
  • 向左查找: ```right = mid – 1```
  • 向右查找: ```left = mid + 1````

1.3.2 Lc69:x的平方根

原题链接

  • 题目描述

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

  • 示例

示例 1:

输入: 4

输出: 2

示例2:

输入: 8

输出: 2

说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

  • 实现

Leetcode题的解法有很多中,比如该题Leetcode官方便给出了三种解决方法,二分法求解只是其中的一种,当然可能不是最优的解法(该题比较牛逼的解法是袖珍计算器法,将开方运算转换为指数+对数运算,数学上很强),但由于我们是为了学习二分法,因此这里使用二分法来求解。

func mySqrt(x int) int {
if x < 0 {
return -1
}
if x == 0 {
return 0
} left, right := 1, x
for left <= right {
mid := left + (right-left)>>1 val := mid * mid
if val < x {
left = mid + 1
} else if val > x {
right = mid - 1
} else {
return mid
}
}
return right
}

关于为什么最后返回right,这里使用一张图进行说明:

  • 复杂度分析

时间复杂度:O(log x),即为二分查找需要的次数。

空间复杂度:O(1)。

1.3.3Lc374:猜数大小

原题链接

  • 题目描述

规则如下:

  1. 每轮游戏,我都会从1到n随机选择一个数字。 请你猜选出的是哪个数字。
  2. 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

  • -1:我选出的数字比你猜的数字小 pick < num
  • 1 :我选出的数字比你猜的数字大 pick > num
  • 0 :我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

  • 示例
示例 1:
输入:n = 10, pick = 6
输出:6 示例 2:
输入:n = 1, pick = 1
输出:1 示例 3:
输入:n = 2, pick = 1
输出:1 示例 4:
输入:n = 2, pick = 2
输出:2
  • 实现
func guessNumber(n int) int {
left, right := 1, n
for left <= right {
mid := left + (right-left)>>1
ret := guess(mid) if ret == 1 {
left = mid + 1
} else if ret == -1 {
right = mid - 1
} else {
return mid
}
}
}
  • 复杂度分析

时间复杂度:O(log n),即为二分查找需要的次数。

空间复杂度:O(1)。

1.3.4 Lc33:搜索旋转数组

原题链接

  • 题目描述

整数数组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 。

  • 示例
示例 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
  • 实现

要点:如果首先找到mid所在的左侧有序区间还是在右侧有序区间,在确定区间之后,根据target和mid的关系(直接判断有序序列,如果不在有序序列范围内,那只能在另一半空间)。

判断mid所在序列比较简单:

如果nums[mid] > nums[0], 则在左侧序列中

如果nums[mid] < nums[0], 则在右侧序列中

func search(nums []int, target int) int {
if nums == nil {
return -1
}
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)/2
if nums[mid] == target {
return mid
}
if nums[mid] >= nums[left] { //mid在左侧序列中
if nums[mid] > target && target >= nums[left] {
right = mid - 1
} else {
left = mid + 1
}
} else{ //mid在右侧序列中
if nums[mid] < target && target <= nums[right] {
left = mid + 1
} else {
right = mid - 1
}
}
}
return -1
}
  • 复杂度分析

时间复杂度:O(logn),其中 n为nums 数组的大小。整个算法时间复杂度即为二分查找的时间复杂度O(logn)。

空间复杂度:O(1)。我们只需要常数级别的空间存放变量。

1.3.5 模板二

func binarySearch2(nums []int, target int) int {
if nums == nil {
return -1
} left, right := 0, len(nums)
for left < right {
mid := left + (right-left)>>1
if nums[mid] < target {
left = mid + 1
} else {
right = mid
}
}
//End Condition: left == right
if left != len(nums)-1 && nums[left] == target {
return left
}
return left
}

该模板是二分查找的高级模板,它用于查找需要需要访问当前索引以及直接右邻居索引的情况。

举一个例子:在一个排序数字中,查找第一次出现n的索引(也可以极值、最值)。代码中判断条件根据不同情形有不同的形式。

  • 二分查找的高级方法
  • 查找条件需要访问元素的直接右邻居
  • 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右查找
  • 保证查询空间的每一步至少有两个元素(left<right)
  • 需要后续处理过程。因为循环体中每一步至少需要两个元素,因此最终只剩下一个元素时需要单独处理,确定是否满足条件。
  • 初始条件: ```left=0, right=length```
  • 终止    : ```left == right```
  • 向左查找: ```right = mid```
  • 向右查找: ```left = mid + 1````

1.3.6 Lc278:第一个错误版本

原题链接

  • 题目描述

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用bool isBadVersion(version)接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

简单的概括下:在数组中,第一次出现x的下标

  • 示例
给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3)-> false
调用 isBadVersion(5)-> true
调用 isBadVersion(4)-> true 所以,4 是第一个错误的版本。
  • 解法
func firstBadVersion(n int) int {
if n < 1 {
return 0
}
left, right := 1, n
for left < right {
mid := left + (right-left)>>1
if isBadVersion(mid) {
right = mid
} else {
left = mid + 1
}
}
return right
}
  • 复杂度分析

时间复杂度:O(logn),整个算法时间复杂度即为二分查找的时间复杂度O(logn)。

空间复杂度:O(1)。

1.3.7Lc162:寻找峰值

题目链接

  • 题目描述

峰值元素是指其值大于左右相邻值的元素。给你一个输入数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

  • 示例
示例 1:
输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。 示例 2:
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。
  • 代码实现

这道题如果说使用二分法可能没有什么思路,后来看了官方题解,才明白过来。这里做一个说明:

这道题可以分为3中情况分别进行分析:单调递增、单调递减、两个组合。如下图所示

而使用二分法的核心便是确定mid所处的区间:递增区间或者递减区间。如果为递增区间,即mid右面一定有一个峰值;如果在递减区间,则在mid左面一定有一个峰值。(题外话:::忽然想到了高数中罗尔中值定理:闭区间连续,开区间可导,端点值相等,导数为零。而导数为零的点就是其中一个极值点)。为什么这么肯定呢?因为题目中假设nums[-1] = nums[n] = -∞,因此上述结论成立,而二分法要做的就是逐步逼近一个峰值点。

func findPeakElement(nums []int) int {
if nums == nil {
return -1
}
left, right := 0, len(nums)-1
for left < right {
mid := left + (right-left)>>1
if nums[mid] < nums[mid+1] {
left = mid + 1
} else {
right = mid
}
}
return left //right is also ok
}

这里right=len(nums)-1,并不是模板中的len(nums).除此之外并没有单独判断只剩一个元素的情况。我们还是要使用马克思主义的精髓:具体问题具体分析

  • 复杂度分析

时间复杂度:O(logn)

空间复杂度:O(1)。

1.3.8 Lc153:寻找旋转排序数组最小值

题目链接

  • 题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组[0,1,2,4,5,6,7] 可能变为[4,5,6,7,0,1,2] 。

请找出其中最小的元素。

提示:

  • 1 <= nums.length <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数都是 唯一 的
  • nums 原来是一个升序排序的数组,但在预先未知的某个点上进行了旋转
  • 示例
示例 1:
输入:nums = [3,4,5,1,2]
输出:1 示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0 示例 3:
输入:nums = [1]
输出:1
  • 代码实现
func findMin(nums []int) int {
if nums == nil {
return 0
}
n := len(nums)
if n == 1 || nums[0] < nums[n-1] {
return nums[0]
} left, right := 0, n-1
for left < right {
mid := left + (right-left)>>1
if nums[mid] > nums[right] {
left = mid + 1
} else {
right = mid
}
}
return nums[left]
}
  • 复杂度分析

时间复杂度:O(logn)

空间复杂度:O(1)。

1.3.9Lc154:寻找旋转排序数组最小值II

题目链接

  • 题目描述

假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组[0,1,2,4,5,6,7]可能变为[4,5,6,7,0,1,2])。

请找出其中最小的元素。

注意数组中可能存在重复的元素。

说明:

这道题是“寻找旋转排序数组中的最小值”的延伸题目。

允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

  • ​​​​​​​​​​​​​​示例
示例 1:
输入:nums = [1,3,5]
输出:1 示例 2:
输入:nums = [2,2,2,0,1]
输出:0
  • 代码实现

说明:153与154区别在于154允许使用重复元素,这会使情况变得复杂些:

此时153的解法便不再适用。需要特别处理nums[mid]==nums[right]的情况,因为最小值在right右边,因此right一步一步向左逼近。

func findMin(nums []int) int {
n := len(nums)
if n == 1 {
return nums[0]
}
left, right := 0, n-1
for left < right {
mid := left + (right-left)>>1
if nums[mid] > nums[right] {
left = mid + 1
} else if nums[mid] < nums[right] {
right = mid
} else {
right--
}
}
return nums[left]
}
  • 复杂度分析

时间复杂度:O(logn)

空间复杂度:O(1)。

1.3.10  模板三

关于模板三,平常使用的比较少,基本使用模板一二就可以搞定,Leetcode官网上给出了这种方案,但是没有给出此模板实现代码。我是用的Java模板转换过来的。由于官方题解中目前还没有看到使用此模板的解法,例子中只是自己的实现,没有完全参考模板,请选择性阅读。

  • ​​​​​​​​​​​​​​​​​​​​​模板代码
int binarySearch(nums []int, target int) {
if (nums == nil || len(nums) == 0)
return -1; left, right := 0, len(nums)- 1;
for left + 1 < right{
// Prevent (left + right) overflow
int mid = left + (right - left) >> 1;
if nums[mid] == target {
return mid;
} else if nums[mid] < target {
left = mid;
} else {
right = mid;
}
}
// Post-processing:
// End Condition: left + 1 == right
if nums[left] == target return left;
if nums[right] == target return right;
return -1;
}
  • 实现二分查找的另一种方法。
  • 搜索条件需要访问元素的直接左右邻居。
  • 使用元素的邻居来确定它是向右还是向左。
  • 保证查找空间在每个步骤中至少有 3 个元素。
  • 需要进行后处理。当剩下2个元素时,循环/递归结束。需要评估其余元素是否符合条件。
  • 初始条件: ```left=0, right=length - 1```
  • 终止    : ```left == right - 1```
  • 向左查找: ```right = mid```
  • 向右查找: ```left = mid````

​​​​​​​​​​​​​​​​​​​​​1.3.11 LC-34:在排序数组中查找元素的第一个和最后一个

原题链接

  • 题目描述

给定一个按照升序排列的整数数组 nums,和一个目标值target。找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值target,返回[-1, -1]。

进阶:

你可以设计并实现时间复杂度为O(log n) 的算法解决此问题吗?

  • 示例
示例 1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4] 示例 2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1] 示例 3:
输入:nums = [], target = 0
输出:[-1,-1]
  • 实现
func searchRange(nums []int, target int) []int {
if nums == nil {
return nil
}
left, right := 0, len(nums)-1
for left <= right {
mid := left + (right-left)>>1 if nums[mid] == target {
right = mid
left = mid
for left > 0 && nums[left-1] == nums[left] {
left--
}
for right < len(nums)-1 && nums[right+1] == nums[right] {
right++
}
return []int{left, right}
} else if nums[mid] < target {
left = mid + 1
} else if nums[mid] > target {
right = mid - 1
}
}
return []int{-1, -1}
}

说明:

实现过程中,在找到目标target情况下,不再使用二分法,而是直接向左向右遍历找到第一个和最后一个出现的位置。之前看到过完全使用二分法的实现,在,完全可以参考模板二中的LC278代码实现两个函数,分别用来查询第一个和最后一个target位置。

  • 复杂度分析

时间复杂度:log(n)

空间复杂度:log(1)

​​​​​​​1.3.12​​​​​​​​​​​​​​LC-658:找到K个最接近的元素

原题链接

  • 题目描述

给定一个排序好的数组arr,两个整数k和x,从数组中找到最靠近x(两数之差最小)的k个数。返回的结果必须要是按升序排好的。

整数a比整数b更接近x需要满足:

|a - x| < |b - x|  或者

|a - x| == |b - x| 且 a < b

  • 示例
示例 1:
输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4] 示例 2:
输入:arr = [1,2,3,4,5], k = 4, x = -1
输出:[1,2,3,4]
  • 代码实现

刷题的过程中写了两种方法,除此之外,在网上题解中看到另外一种二分法解题思路,很简洁,因此也会将其收录下来学习下。

  • 二分法+双指针
  • 双指针
  • 二分法

下面分别给出三种实现方式:

二分法+双指针

func findClosestElements2(arr []int, k int, x int) []int {
n := len(arr)
if n < k {
return nil
}
if arr[0] > x {
return arr[:k] /*左闭右开*/
}
if arr[n-1] < x {
return arr[n-k:]
} left, right := 0, n-1
for left+1 < right {
mid := left + (right-left)>>1
if arr[mid] > x {
right = mid
} else if arr[mid] < x {
left = mid
} else {
break
}
}
lk, rk := 0, 0
if left+1 == right { /*x不存在, left<x<right*/
lk, rk = left-k+1, right+k-1
} else { //x存在
mid := left + (right-left)>>1
lk, rk = mid-k+1, mid+k-1
} if lk < 0 {
lk = 0
}
if rk > n-1 {
rk = n - 1
}
for lk+k-1 < rk {
if x-arr[lk] <= arr[rk]-x {
rk--
} else {
lk++
}
}
return arr[lk : rk+1]
}

自己的二分法+双指针代码实现比较啰嗦,除此之外也尝试了下将这者完全融合成一段代码实现,但是非常遗憾,水平有限并没有成功。

双指针

双指针解法也是比较容易理解的。从数组两端分别开始遍历,将里目标值x比较远的元素去除,循环迭代直到长度满足要求为止。代码实现也比较简单,时间复杂度为O(n),空间复杂度为O(1)。当元素数量特别多时,不如二分法效率高。

func findClosestElements(arr []int, k int, x int) []int {
n := len(arr)
if n < k {
return nil
}
if arr[0] > x {
return arr[:k] /*左闭右开*/
}
if arr[n-1] < x {
return arr[n-k:]
}
//{1, 2, 3, 4, 5}, 4, 3)
left, right := 0, n-1
for left+k-1 < right {
if x-arr[left] <= arr[right]-x {
right--
} else {
left++
}
} //End Condition: left+k-1 == right
return arr[left : right+1]
}

二分法

这个二分法实现虽然仍然不同于模板三,但是比较接近,同时很简洁,非常值得学习一下。(解题思路:二分查找左边界的开始,注意不是查找区间,而是查找正确区间的左值)

func findClosestElements(arr []int, k int, x int) []int {
start := 0
end := len(arr) - k for start < end {
mid := start + (end-start)/2 if x-arr[mid] > arr[mid+k]-x {
start = mid + 1
} else {
end = mid
}
}
return arr[start : start+k]
}

时间复杂度:log(n)

空间复杂度:log(1)​​​​​​​​​​​​​​​​​​​​​​​​​​​​

​​​​​​​1.4​​​​​​​小结

很多二分查找问题都归结于这三种模板中的一个,有一些问题也可以使用多个模板进行解决(比如模板三种的例子,我都是其他其他模板解决的)。三个模板的主要差别在于:

  • 左右中索引的分配
  • 循环或者递归终止条件不同
  • 后续处理的必要性(做题时很多根本不需要后续处理)

模板 #1 (left <= right)

  • 二分查找的最基础和最基本的形式。
  • 查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
  • 不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。

模板 #2 (left < right)

  • 一种实现二分查找的高级方法。
  • 查找条件需要访问元素的直接右邻居。
  • 使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
  • 保证查找空间在每一步中至少有 2 个元素。
  • 需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。

模板 #3 (left + 1 < right)

  • 实现二分查找的另一种方法。
  • 搜索条件需要访问元素的直接左右邻居。
  • 使用元素的邻居来确定它是向右还是向左。
  • 保证查找空间在每个步骤中至少有 3 个元素。
  • 需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素是否符合条件。

模板一

在有序序列中查找某一个数

模板二

在有序序列中寻找第一次出现的数(修改后也可以最后一次)

模板三

在有序序列中查询最值

 

我整理的是Word版本的,但是Word到MD格式全乱了,如果有需要word版本的,欢迎留言。Word版本内容持续更新中

 
 
 
 
 
 
 
 
 
 
 
 

LeetCode刷题模板(1):《我要打10个》之二分法的更多相关文章

  1. LeetCode刷题总结-树篇(中)

    本篇接着<LeetCode刷题总结-树篇(上)>,讲解有关树的类型相关考点的习题,本期共收录17道题,1道简单题,10道中等题,6道困难题. 在LeetCode题库中,考察到的不同种类的树 ...

  2. 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题

    大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...

  3. LeetCode刷题专栏第一篇--思维导图&时间安排

    昨天是元宵节,过完元宵节相当于这个年正式过完了.不知道大家有没有投入继续投入紧张的学习工作中.年前我想开一个Leetcode刷题专栏,于是发了一个投票想了解大家的需求征集意见.投票于2019年2月1日 ...

  4. leetcode 刷题进展

    最近没发什么博客了 凑个数 我的leetcode刷题进展 https://gitee.com/def/leetcode_practice 个人以为 刷题在透不在多  前200的吃透了 足以应付非算法岗 ...

  5. LeetCode刷题指南(字符串)

    作者:CYC2018 文章链接:https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/Leetcode+%E9%A2%98%E8%A7% ...

  6. leetcode刷题记录--js

    leetcode刷题记录 两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但 ...

  7. LeetCode刷题总结之双指针法

    Leetcode刷题总结 目前已经刷了50道题,从零开始刷题学到了很多精妙的解法和深刻的思想,因此想按方法对写过的题做一个总结 双指针法 双指针法有时也叫快慢指针,在数组里是用两个整型值代表下标,在链 ...

  8. Leetcode刷题记录(python3)

    Leetcode刷题记录(python3) 顺序刷题 1~5 ---1.两数之和 ---2.两数相加 ---3. 无重复字符的最长子串 ---4.寻找两个有序数组的中位数 ---5.最长回文子串 6- ...

  9. LeetCode刷题总结-数组篇(上)

    数组是算法中最常用的一种数据结构,也是面试中最常考的考点.在LeetCode题库中,标记为数组类型的习题到目前为止,已累计到了202题.然而,这202道习题并不是每道题只标记为数组一个考点,大部分习题 ...

随机推荐

  1. Spring Cloud分区发布实践(2) 微服务

    我们准备一下用于查询姓名的微服务. 首先定义一下服务的接口, 新建一个空的Maven模块hello-remotename-core, 里面新建一个类: public interface RemoteN ...

  2. MVC从客户端中检测到有潜在危险的Request.Form值的解决方法

    1.ASPX页面 在页面头部的page中加入ValidateRequest="false" 2.web.config中配置validateRequest="false&q ...

  3. 桌面小部件AppWidgetProvider简单分析

    1.一般桌面小部件涉及到的类 AppWidgetProvider :BroadcastRecevier子类,用于接收更新,删除通知 AppWidgetProvderInfo:AppWidget相关信息 ...

  4. i春秋CTF-web-upload

    ------------恢复内容开始------------ 记一道文件上传的题目. 题目告诉我们随意上传,第一想到的当然给他上传一个木马文件然后蚁剑拿shell,上传之后可以在源码里查看我们上传的文 ...

  5. AQS学习(一)自旋锁原理介绍(为什么AQS底层使用自旋锁队列?)

    1.什么是自旋锁? 自旋锁作为锁的一种,和互斥锁一样也是为了在并发环境下保护共享资源的一种锁机制.在任意时刻,只有一个执行单元能够获得锁. 互斥锁通常利用操作系统提供的线程阻塞/唤醒机制实现,在争用锁 ...

  6. SpringMVC/boot-CSRF安全方案

    1. CSRF原理与防御方案概述 一. 原理 增删改的接口参数值都有规律可循,可以被人恶意构造增删改接口 将恶意构造的增删改接口发给对应特定用户,让特定用户点击 特定用户使用自己的认证信息对该接口发起 ...

  7. Linux下的段错误(Segmentation fault)

    Linux下的段错误(Segmentation fault) 段错误是指:访问了系统分配给程序的内存空间之外起的内存空间,比如: 访问不存在的地址 访问受系统保护的地址 访问了只读内存地址 内存访问越 ...

  8. STM32—中断详解(配合按键中断代码,代码亲测)

    在STM32中执行中断主要分三部分: 1.配置NVIC_Config()函数 2.配置EXTI_Config()函数 3.编写中断服务函数 (注:本文章所用代码为中断按键代码,实现了按键进入中断从而控 ...

  9. java导出excel(easypoi)

    介绍 easypoi功能如同名字easy,主打的功能就是容易,让一个没见接触过poi的人员 就可以方便的写出Excel导出,Excel模板导出,Excel导入,Word模板导出,通过简单的注解和模板 ...

  10. 【小技巧】java的List分页

    今天,工作上,由于业务的一些特殊性,需要拿到数据后在java代码中进行分页. 写了一个工具类,记录如下: import java.util.ArrayList; import java.util.Li ...