**
前言:Golang编写
leetcode
题目:162. 寻找峰值
二分解法:时间复杂度:O(log n),空间复杂度:O(1)
逻辑说明:
当找峰值的时候,因为题意说了只要找一个就行。且nums[-1]和nums[len(nums)]表示为负无穷大,所以可以考虑以下几种情况:
- 当整个数组都是递增或者递减的时候,那么峰值肯定是头元素或者是尾元素,因为边缘界限都是负无穷,且旁边相邻的数还比自身小。
- 当数组长度为1的时候,直接返回left开头值。
- 当峰值在数组中间的时候(旁边都有元素),这个时候我们考虑用二分,题意也说了时间复杂度必须得是log n,所以指明了是要用二分,二分查找有很多种方法,最重要的是取决于边界条件如何去写(left,right什么时候变化),如果要找一个峰值,那么我们要找的数肯定是一个递增序列的最后端的值,所以这个时候思路就可以出来,当nums[mid] < nums[mid+1],那么left的值应该往右(哪边大往哪走,大的区间肯定包含递增序列的峰值)走
func findPeakElement(nums []int) int {
left, right := 0, len(nums)-1
for left < right {
mid := (left + right) / 2
if nums[mid] < nums[mid+1] {
left = mid + 1
} else {
right = mid
}
}
return left
}
题目:153. 寻找旋转排序数组中的最小值
线性解法:时间复杂度:O(n),空间复杂度:O(1)
逻辑说明:
如果用时间复杂度为O(n)的办法来解决,其实比较简单,复杂的就是一点边界条件,直接从前边遍历到后边,因为数组本身是有序的,只是旋转了一下,所以如果满足nums[i] > nums[i+1], 那么nums[i+1]就是我们要找的最小值,当数组长度为1,那么直接返回该唯一元素。
func findMin(nums []int) int {
ans := nums[0]
if len(nums) == 1 {
return nums[0]
}
for i := 0;i < len(nums)-1;i++ {
if nums[i] > nums[i+1] {
ans = nums[i+1]
}
}
return ans
}
二分解法:时间复杂度:O(log n),空间复杂度:O(1)
逻辑说明:
用二分来写的话需要最重要的是理清楚判断条件,根据题意我们可以试着推测:
- 当 nums[mid] < nums[high]时,说明最小值就在mid这个位置或者mid之前,所以high = mid
- 当 nums[mid] > nums[high]时,说明最小值在mid之后(此时不包括mid)
- 依次循环查找,当low == high时,此时就已经找到了最小值。
func findMin(nums []int) int {
low, high := 0, len(nums) - 1
for low < high {
mid := (high + low) / 2
if nums[mid] < nums[high] {
high = mid
} else {
low = mid + 1
}
}
return nums[low]
}
题目:剑指 Offer II 004. 只出现一次的数字
排序比较解法:时间复杂度:O(nlog n),时间复杂度:O(1)
逻辑说明:
先排序,然后找到左右相邻均不等的元素。
func singleNumber(nums []int) int {
if len(nums) == 1 {
return nums[0]
}
sort.Ints(nums)
if nums[0] != nums[1] {
return nums[0]
}
index := -1
for i := 1;i < len(nums)-1;i++ {
if nums[i-1] != nums[i] && i+1 < len(nums) && nums[i] != nums[i+1] {
index = i
}
}
if index == -1 { index = len(nums)-1 }
return nums[index]
}
map解法:时间复杂度:O(n),时间复杂度:O(n)
func singleNumber(nums []int) int {
// 创建map,键存元素值,值存出现次数
freq := map[int]int{}
// 如果出现相同的元素,个数加一
for _, v := range nums {
freq[v]++
}
// 输出map值只有1个的数字
for num, occ := range freq {
if occ == 1 {
return num
}
}
return 0 // 不会发生,数据保证有一个元素仅出现一次
}
二进制解法:时间复杂度:O(nlog k),空间复杂度:O(1)
又因为该题指明了最大范围,所以log k最大值固定(32),故时间复杂度也可以说为32n = O(n)
逻辑说明:
采用二进制解法,运用位运算符的特点来解决:
首先我们可以确定了数组里所有的值都在2^32-1内,所以外层循环为32。
其次,了解位运算符,以及二进制的特点,
我们最后求出来的答案自然也可以由32位二进制数字(0/1)来组成
核心算法:我们要用total把数组里的所有数字的同一位二进制位上的数字相加,至于如何求出这一位数字呢? num >> i & 1,num>>i是求出某一个数组值右移 i 位之后的结果,此时我们所要的第 i 个二进制值就是num >> i 的最后一位上,下面怎么把这最后一位抠出来呢,那就是接着和 1 进行与运算:
举个例子:
假如我们要求出5这个数字的第1位(从右往左数的第一位,即从低位到高位,最低那一位是第0位)
5的二进制写法是:101,那么5 >> 1 就是010,整体往后移了一位,前方补0,这个时候我们要的第一位也就是0现在在末尾的位置,下面对010和1进行&运算,即让010和001进行&运算,上方的表格中说了两位均为1才为1,其余情况全是0,那么:
010(5 >> 1)
001(1)
000(结果)
这不就把咱想要的第一位就求出来啦,以此类推
下面回到题意中,我们把数组里面所有同一位上的值全部加起来,最后和3进行求余,因为只有一个数字只有一个,其他都是三个三个的出现,求余之后的结果肯定就是我们答案的某一位二进制值,如果求余之后的结果等于1,那就通过 << 运算符从末尾上回到第 i 位,和 ans 进行或运算,这就把 1 填上去了,如果等于0也没事,因为二进制的某一位的值默认为0,以此类推,把每一位的值都拼接到一起,最后就是我们真正想要的ans,最后返回就行啦。
func singleNumber(nums []int) int {
ans := 0
for i := 0; i <= 31; i++ {
total := 0
for _, num := range nums {
total += num >> i & 1
}
if total%3 == 1 {
ans |= 1 << i
}
}
return ans
}
题目:剑指 Offer II 005. 单词长度的最大乘积
位运算+预计算解法:时间复杂度:O((m+n)* n ),空间复杂度:O(n)
逻辑说明:
可以这样想,一个二进制位,可以代替一个字母是否出现,1则有,0则无
预计算的过程其实和上方题目的ans填补1是一样的,首先遍历整个数组,取出每一个字符串,然后遍历每个字符串,如果出现某一个字符,则把某一位置位1,假如字符串为abce,则把第0,1,2,4位置位1(也是从低位到高位的顺序来算,当然从高位到低位也行,因为这道题不是求两数之和的,而是判断相同的位置又没有重复),
核心语句1:bitMask |= 1 << (words[i][j] - ‘a’)
words[i][j] - 'a’是求需要进多少位,然后1 << (words[i][j] - ‘a’) 就是将该二进制位置位1,最后通过或运算,将1填到bitmask中,以此类推,每循环过一个单词,就有一个唯一的bitmask与之对应,然后放入masksMap中,为什么有一个if条件,因为题意中要找的是最大值,比如:单词 “ab” 和 “aaabbbab”是同一个bitmask,所以为了最大值,我们当然是取长单词的长度。
核心语句2:(x & y) == 0
这个属于判断条件,前面的准备工作做好之后,接下来就是到比较的环节中了,通过两个循环语句遍历masksMap,依次一一比较,怎么比较才能知道两个单词不同呢,这就是核心语句2的作用了,如果熟悉与运算的用法,那一看便知,如果不是很理解,我就通俗易懂的说,当两个二进制数里的每一位的值都不同,那么最后返回0,如果有一位或者多位不一样,那么将返回另外一个数,比较简单,判断条件有了,那么就当两个数与运算之后为0之后,就把长度相乘,然后答案随时随刻取最大值,最后返回就行。
func maxProduct(words []string) int {
n := len(words)
masksMap := make(map[int]int)
for i := 0; i < n; i++ {
bitMask := 0
for j := 0; j < len(words[i]); j++ {
bitMask |= 1 << (words[i][j] - 'a')
}
if len(words[i]) > masksMap[bitMask] {
masksMap[bitMask] = len(words[i])
}
}
ans := 0
for x, xItem := range masksMap {
for y, yItem := range masksMap {
if (x & y) == 0 {
len := xItem * yItem
if len > ans {
ans = len
}
}
}
}
return ans
}
题目:剑指 Offer II 006. 排序数组中两个数字之和
双指针解法:时间复杂度:O(n),空间复杂度:O(1)
逻辑说明:
比较简单,递增序列,双指针,left和right,初始值为开头和结尾,当两数之和大于target的时候,right–,两数之和小于target的时候,left++,直到等于target,然后将left和right装进ans数组中,最后返回即可。
func twoSum(numbers []int, target int) []int {
ans := make([]int, 2)
lp, rp := 0, len(numbers)-1
for {
if(numbers[lp]+numbers[rp] > target){
rp--
} else if(numbers[lp]+numbers[rp] < target){
lp++
} else {
ans[0] = lp
ans[1] = rp
break
}
}
return ans
}