2022年寒假算法练习第二天(1.11)

**
前言:Golang编写

leetcode

题目:162. 寻找峰值
题意

二分解法:时间复杂度:O(log n),空间复杂度:O(1)
逻辑说明:

当找峰值的时候,因为题意说了只要找一个就行。且nums[-1]和nums[len(nums)]表示为负无穷大,所以可以考虑以下几种情况:

  1. 当整个数组都是递增或者递减的时候,那么峰值肯定是头元素或者是尾元素,因为边缘界限都是负无穷,且旁边相邻的数还比自身小。
  2. 当数组长度为1的时候,直接返回left开头值。
  3. 当峰值在数组中间的时候(旁边都有元素),这个时候我们考虑用二分,题意也说了时间复杂度必须得是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)
逻辑说明:

用二分来写的话需要最重要的是理清楚判断条件,根据题意我们可以试着推测:

  1. 当 nums[mid] < nums[high]时,说明最小值就在mid这个位置或者mid之前,所以high = mid
  2. 当 nums[mid] > nums[high]时,说明最小值在mid之后(此时不包括mid)
  3. 依次循环查找,当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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值