剑指offer--golang解答

参考:剑指offer书
https://github.com/DinghaoLI/Coding-Interviews-Golang
面试题11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0

func minArray(numbers []int) int {
	left := 0
	right := len(numbers) - 1
	middle := 0

	for numbers[left] >= numbers[right] {

		if right-left == 1 {
			middle = right
			break
		}

		middle = (left + right) / 2
		if numbers[left] == numbers[middle] && numbers[right] == numbers[middle] {
			return inOrder(numbers, left, right)
		}
		if numbers[middle] >= numbers[left] {
			left = middle
		} else if numbers[middle] <= numbers[right] {
			right = middle
		}

	}
	return numbers[middle]
}

func inOrder(numbers []int, left, right int) int {
	result := numbers[left]
	for i := left + 1; i < right; i++ {
		if numbers[i] < result {
			result = numbers[i]
		}
	}
	return result
}

动态规划

面试题10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21

func numWays(n int) int {
	if n <= 0 {
		return 1
	}
	if n == 1 || n == 2 {
		return n
	}
	dp := make([]int, n+1)
	dp[1] = 1
	dp[2] = 2

	for i := 3; i <= n; i++ {
		dp[i] = (dp[i-1] + dp[i-2])%1000000007
	}
	return dp[n]
}

深度优先搜索

面试题12. 矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true

func exist(board [][]byte, word string) bool {
	wordByte := []byte(word)
	for i := 0; i < len(board); i++ {
		for j := 0; j < len(board[0]); j++ {
			if dfs1(board, wordByte, i, j, 0) {
				return true
			}
		}
	}
	return false

}

func dfs1(board [][]byte, word []byte, i, j, k int) bool {
	if i < 0 || i > len(board)-1 || j < 0 || j > len(board[0])-1 || board[i][j] != word[k] {
		return false
	}

	if k == len(word)-1 {
		return true
	}

	tmp := board[i][j]

	board[i][j] = '/'
	res := dfs1(board, word, i+1, j, k+1) || dfs1(board, word, i-1, j, k+1) || dfs1(board, word, i, j-1, k+1) || dfs1(board, word, i, j+1, k+1)
	board[i][j] = tmp

	return res
}

面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1
输出:3
参考:https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/solution/mian-shi-ti-13-ji-qi-ren-de-yun-dong-fan-wei-dfs-b/
分析:深度优先遍历 DFS
深度优先搜索: 可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为 可行性剪枝 。
算法解析:
递归参数: 当前元素在矩阵中的行列索引 i 和 j ,两者的数位和 si, sj 。
终止条件: 当 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访问过 时,返回 00 ,代表不计入可达解。
递推工作:
标记当前单元格 :将索引 (i, j) 存入 Set visited 中,代表此单元格已被访问过。
搜索下一单元格: 计算当前元素的 下、右 两个方向元素的数位和,并开启下层递归 。
回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数,代表从本单元格递归搜索的可达解总数。
在这里插入图片描述

func movingCount(m int, n int, k int) int {

	visited := make([][]bool, m+1)
	for i := range visited {
		visited[i] = make([]bool, n+1)
	}
	return dfs2(0, 0, 0, 0, m, n, k, visited)

}

func dfs2(i, j, si, sj, m, n, k int, visited [][]bool) int {
	if i >= m || j >= n || si+sj > k || visited[i][j] {
		return 0
	}
	visited[i][j] = true

	return 1 + dfs2(i+1, j, getNext(i, si), sj, m, n, k, visited) + dfs2(i, j+1, si, getNext(j, sj), m, n, k, visited)
}

func getNext(i, si int) int {
	if (i+1)%10 == 0 {
		si = si - 8
	} else {
		si = si + 1
	}
	return si
}

leetcode_200. 岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1

//深度优先遍历
func numIslands(grid [][]byte) int {
	count := 0
	for i := 0; i < len(grid); i++ {
		for j := 0; j < len(grid[0]); j++ {
			if grid[i][j] == '1' {
				dfs(grid, i, j)
				count++
			}

		}
	}
	return count

}

func dfs(grid [][]byte, x, y int) {
	//终止条件越过边界和超过岛屿时return,跟二叉树root==nil return一个道理
	if x < 0 || y < 0 || x >=len(grid) || y >=len(grid[0]) || grid[x][y] == '0' {
		return
	}

	grid[x][y] = '0' //把遍历到的岛屿变为海洋,防止重复遍历到
	//上下左右
	dfs(grid, x+1, y)
	dfs(grid, x-1, y)
	dfs(grid, x, y-1)
	dfs(grid, x, y+1)
}

面试题14.剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
分析:
https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/mian-shi-ti-14-i-jian-sheng-zi-tan-xin-si-xiang-by/

// 尽可能将绳子以长度 33 等分为多段时,乘积最大。
func cuttingRope(n int) int {
	if n <= 3 {
		return n - 1
	}
	a := n / 3 //n可以拆分成几个3
	b := n % 3
	if b == 0 { //n能被3整除
		return int(math.Pow(3, float64(a)))
	}
	if b == 1 { //n除3余1,因为2*2=1*3,所以把最后一个3倍和余数1拆成2+2
		return int(math.Pow(3, float64(a-1))) * 4
	}
	return int(math.Pow(3, float64(a))) * 2 //n除3余2
}

面试题15. 二进制中1的个数

请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 ‘1’。
分析:示例n&(n-1)每次都会消除最右边的1在这里插入图片描述

func hammingWeight(num uint32) int {
	n := 0
	for num != 0 {
		n++
		num = num & (num - 1)
	}
	return n
}

面试题16. 数值的整数次方

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
示例 1:
输入: 2.00000, 10
输出: 1024.00000
在这里插入图片描述在这里插入图片描述

func myPow(x float64, n int) float64 {
    res := 1.0
	if n == 0 {
		return res
	}
	if n < 0 {
		x = 1 / x
		n = -n
	}
	for n > 0 {
		if n&1 == 1 {
			res *= x
		}
		x *= x
		n = n >> 1
	}
	return res
}

面试题17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]


面试题20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。

func isNumber(s string) bool {
	if s == "" {
		return false
	}
	str := strings.TrimSpace(s)
	num := false
	dot := false
	e := false

	for i, v := range str {
		if v >= '0' && v <= '9' {
			num = true
		} else if v == '.' { //点之前不能出现点和e
			if dot == true || e == true {
				return false
			}
			dot = true
		} else if v == 'e' || v == 'E' { // e之前不能出现e或者必须出现数
			if e == true || !num {
				return false
			}
			e = true
			num = false //重置numSeen,排除123e或者123e+的情况,确保e之后也出现数
		} else if v == '-' || v == '+' { //在0位置或者在e的后面出现
			if i != 0 && str[i-1] != 'e' && str[i-1] != 'E' {
				return false
			}
		} else {
			return false
		}
	}
	return num
}

面试题42. 连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n).
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
动态规划:

func maxSubArray1(nums []int) int {
	if nums == nil || len(nums) == 0 {
		return 0
	}

	max := nums[0]
	dp := make([]int, len(nums))
	dp[0] = nums[0]

	for i := 1; i < len(nums); i++ {
		dp[i] = getMax(nums[i], dp[i-1]+nums[i])
		if max < dp[i] {
			max = dp[i]
		}
	}

	return max
}

func getMax(a, b int) int {
	max := a
	if b > a {
		max = b
	}
	return max
}

49、丑数

我们把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数

方法一: 该算法非常直观,代码也非常简洁,但最大的问题每个整数都需要计算。即使一个数字不是丑数,我们还是需要对它做求余数和 除法操作。因此该算法的时间效率不是很高

func nthUglyNumber(n int) int {
	number := 0
	found := 0
	for found < n {
		number++
		if uglyNumber(number) {
			found++
		}
	}
	return number
}

func uglyNumber(num int) bool {
	for num%2 == 0 {
		num /= 2
	}
	for num%3 == 0 {
		num /= 3
	}
	for num%5 == 0 {
		num /= 5
	}

	return num == 1
}

方法二:已有的丑数是按顺序存放在数组中的。对乘以 2而言,肯定存在某一个丑数T2,排在它之前的每一个丑数乘以2得到的
结果都会小于已有最大的丑数,在它之后的每一个丑数乘以2得到的结果都会太大。我们只需记下这个丑数的位置,同时每次生成新的丑数的 时候,去更新这个T2。对乘以3和5而言,也存在着同样的T3和T5
以空间换时间

func nthUglyNumber1(n int) int {
 u2, u3, u5, nextUglyIndex := 0, 0, 0, 1
	uglyArray := make([]int, n)
	uglyArray[0] = 1
	for nextUglyIndex < n {
		uglyArray[nextUglyIndex] = min3(uglyArray[u2]*2, uglyArray[u3]*3, uglyArray[u5]*5)

		for uglyArray[u2]*2 <= uglyArray[nextUglyIndex] {
			u2++
		}
		for uglyArray[u3]*3 <= uglyArray[nextUglyIndex] {
			u3++
		}
		for uglyArray[u5]*5 <= uglyArray[nextUglyIndex] {
			u5++
		}
		nextUglyIndex++
	}
	return uglyArray[n-1]

}

func min3(a, b, c int) int {
	min := a
	if b < min {
		min = b
	}
	if c < min {
		min = c
	}
	return min
}

50、第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
示例:
s = “abaccdeff”
返回 “b”
s = “”
返回 " "

func firstUniqChar(s string) byte {
	var list [26]int
	for i := 0; i < len(s); i++ {
		list[s[i]-'a']++
	}
	for j := 0; j < len(s); j++ {
		if list[s[j]-'a'] == 1 {
			return s[j]
		}
	}
	return ' '
}

51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
分析:
图中省略了最后一步,即复制第二个子数组最后剩余的4到辅助数组中。(a)P1指 向的数字大于P2指向的数字,表明数组中存在逆序对。P2指向的数字是第二个子数组的第二个 数字,因此第二个子数组中有两个数字比7小。把逆序对数目加2,并把7复制到辅助数组,向前 移动P1和P3。(b)P1指向的数字小于P2指向的数字,没有逆序对。把P2指向的数字复制到辅 助数组,并向前移动P2和P3。(c)P1指向的数字大于P2指向的数字,因此存在逆序对。由于 P2指向的数字是第二个子数组的第一个数字,子数组中只有一个数字比5小。把逆序对数目加 1,并把5复制到辅助数组,向前移动P1和P3。 接下来我们统计两个长度为2的子数组之间的逆序对。我们在图5.2 中细分图5.1(d)的合并子数组及统计逆序对的过程。
在这里插入图片描述我们先用两个指针分别指向两个子数组的末尾,并每次比较两个指 针指向的数字。如果第一个子数组中的数字大于第二个子数组中的数 字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的 个数(如图5.2(a)和图5.2(c)所示)。如果第一个数组中的数字小 于或等于第二个数组中的数字,则不构成逆序对(如图5.2(b)所 示)。每一次比较的时候,我们都把较大的数字从后往前复制到一个辅 助数组中去,确保辅助数组中的数字是递增排序的。在把较大的数字复 制到辅助数组之后,把对应的指针向前移动一位,接下来进行下一轮比 较。
参考:很像归并排序https://emacsist.github.io/2016/11/22/golang-%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8Fmergesort/
实现:

func reversePairs(nums []int) int {
    if len(nums) <= 0 || nums == nil {
		return 0
	}

	copy := make([]int, len(nums))
	for k, v := range nums {
		copy[k] = v
	}

	return reversePairCore(nums, copy, 0, len(nums)-1)
}

func reversePairCore(data, copy []int, start, end int) int {
	if start == end {
		copy[start] = data[start]
		return 0
	}

	halfLen := (end - start) / 2
	left := reversePairCore(copy, data, start, start+halfLen)  //分成两半,前半部分
	right := reversePairCore(copy, data, start+halfLen+1, end) //后半部分

	i := start + halfLen //模拟前半部分的最后一个
	j := end             //模拟后半部分的最后一个
	copyIndex := end     //写入辅助数组时大数写在后,确保辅助数组递增
	count := 0

	for i >= start && j >= start+halfLen+1 {
		if data[i] > data[j] { //如果第一个子数组中的数字大于第二个子数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数
			copy[copyIndex] = data[i]
			i--
			copyIndex--
			count += j - start - halfLen //j-(start+halfLen+1)+1 j是后半部分的尾,start+halfLen+1是后半部分的头,尾-头+1
		} else { //就没有逆序数了
			copy[copyIndex] = data[j]
			j--
			copyIndex--
		}
	}

	for ; i >= start; i-- {
		copy[copyIndex] = data[i]
		copyIndex--
	}

	for ; j >= start+halfLen+1; j-- {
		copy[copyIndex] = data[j]
		copyIndex--
	}

	return left + right + count

}

52、两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在这里插入图片描述
在节点 c1 开始相交。
分析:我们可以先遍历一次得到它们的长度 分别为5和4,也就是较长的链表与较短的链表相比多一个结点。第二次 先在长的链表上走1步,到达结点2。接下来分别从结点2和结点4出发同 时遍历两个结点,直到找到它们第一个相同的结点6,这就是我们想要 的结果。
在这里插入图片描述

func getIntersectionNode(headA, headB *ListNode18) *ListNode18 {
	lenA := getLength(headA)
	lenB := getLength(headB)
	lenDiff := lenA - lenB

	listLong := headA
	listShort := headB
	if lenA < lenB {
		listLong = headB
		listShort = headA
		lenDiff = lenB - lenA
	}

	for i := 0; i < lenDiff; i++ {
		listLong = listLong.next
	}

	//长度一样后同时前进,O(m+n)
	for listLong != nil && listShort != nil && listLong != listShort {
		listLong = listLong.next
		listShort = listShort.next
	}

	return listLong
}

func getLength(head *ListNode18) int {
	length := 0
	for head != nil {
		length++
		head = head.next
	}
	return length
}

面试题53 - I. 在排序数组中查找数字 I

统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
分析:我们思考如何更好地利用二分查找算法。假设我们是统计数 字k在排序数组中出现的次数.用二分查找算法直接找到第一个k及最后一个k,我们先分析如何用二分查找算法在数组中找到第一个k。二分查找 算法总是先拿数组中间的数字和k作比较。如果中间的数字比k大,那么 k只有可能出现在数组的前半段,下一轮我们只在数组的前半段查找就 可以了。如果中间的数字比k小,那么k只有可能出现在数组的后半段, 下一轮我们只在数组的后半段查找就可以了。如果中间的数字和k相等 呢?我们先判断这个数字是不是第一个k。如果位于中间数字的前面一 个数字不是k,此时中间的数字刚好就是第一个k。如果中间数字的前面 一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮我们仍 然需要在数组的前半段查找。
我们可以用同样的思路在排序数组中找到最后一个k。如果中间数 字比k大,那么k只能出现在数组的前半段。如果中间数字比k小,k就只 能出现在数组的后半段。如果中间数字等于k呢?我们需要判断这个k是 不是最后一个k,也就是中间数字的下一个数字是不是也等于k。如果下 一个数字不是k,则中间数字就是最后一个k了;否则下一轮我们还是要 在数组的后半段中去查找。

length := len(nums)
	number := 0

	if length == 0 {
		return 0
	}

	first := getFirstIndex(nums, length, target, 0, length-1)
	last := getLastIndex(nums, length, target, 0, length-1)

	if first > -1 && last > -1 {
		number = last - first + 1
	}
	return number
}

//找target的第一个元素
func getFirstIndex(nums []int, length, target, start, end int) int {
	if start > end {
		return -1
	}

	mid := (start + end) / 2
	if nums[mid] == target {
		if (mid > 0 && nums[mid-1] != target) || mid == 0 { //如果前一个值不是target,说明mid就是第一个target
			return mid
		} else { //如果前一个值也是target,也就是说第一个k肯定在数组的前半段,下一轮仍然需要在数组的前半段查找。
			end = mid - 1
		}
	} else if nums[mid] < target { //中间值比target小了,target在后半部分
		start = mid + 1
	} else {
		end = mid - 1
	}
	return getFirstIndex(nums, length, target, start, end)
}

//找target的最后一个元素
func getLastIndex(nums []int, length, target, start, end int) int {
	if start > end {
		return -1
	}
	mid := (start + end) / 2
	if nums[mid] == target {
		if (mid < length-1 && nums[mid+1] != target) || mid == length-1 { //如果后一个值不是target,说明mid就是最后一个target
			return mid
		} else { //如果后一个值也是target,也就是说最后一个k肯定在数组的后半段,下一轮仍然需要在数组的后半段查找。
			start = mid + 1
		}
	} else if nums[mid] < target { //中间值比target小了,target在后半部分
		start = mid + 1
	} else {
		end = mid - 1
	}
	return getLastIndex(nums, length, target, start, end)
}

面试题53 - II. 0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
分析:有序数组也可以用二分法

func missingNumber(nums []int) int {
	if len(nums) == 0 {
		return 0
	}
	result, v := 0, nums[0]
	for result, v = range nums {
		if result != v {
			return result
		}
	}
	return result + 1
}

面试题56 - I. 数组中数字出现的次数

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
分析:
一个整型数组里除了两个数字之外,其他的数字都出现了两次
此题考察的是异或运算的特点:即两个相同的数异或结果为0。
此题用了两次异或运算特点:
第一次使用异或运算,得到了两个只出现一次的数相异或的结果。
因为两个只出现一次的数肯定不同,即他们的异或结果一定不为0,一定有一个位上有1。
另外一个此位上没有1,我们可以根据此位上是否有1,将整个数组重新划分成两部分,
一部分此位上一定有1,另一部分此位上一定没有1,
然后分别对每部分求异或,因为划分后的两部分有这样的特点:
其他数都出现两次,只有一个数只出现一次。
因此,我们又可以运用异或运算,分别得到两部分只出现一次的数。

func singleNumbers(nums []int) []int {
     xor := 0
    for _,v := range nums {
        xor ^= v
    }    
    // 取xor最低位为1的数
    lowest := xor & -xor
    a,b := 0,0
    for _, v := range nums {
        if lowest&v == 0 {
            a ^= v
        } else {
            b ^= v
        }
    }
    return []int{a,b}
}

面试题56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
分析:一个整型数组里除了一个数字之外,其他的数字都出现了三次如果是出现两次的话,用一个bit就可以比如ones,初始为0
当 5 第 1 次出现, ones = 5
当 5 第 2 次出现, ones 清空为 0,表示 ones 可以去处理其他数字了
所以,最后 如果 ones != 0的话, ones 记录的就是只出现了一次的那个数字
公式是 ones = ones xor i
那么,如果是三次的话,香农定理,需要用 2 bits 进行记录
当5第1次出现的时候,ones = 5, twos = 0, ones 记录这个数字
当5第2次出现的时候,ones = 0, twos = 5, twos 记录了这个数字
当 5 第 3 次出现的时候,ones = 0, twos = 0, 都清空了,可以去处理其他数字了
所以,如果有某个数字出现了 1 次,就存在 ones 中,出现了 2 次,就存在 twos 中
公式方面, 上面 2 次的时候,ones 清空的公式是 ones = ones xor i
而第 3 次时, ones 要等于零, 而这时 twos 是 True , 所以再 & 一个 twos 的非就可以, ones = (ones xor i) & ~twos
所以,总的公式是
ones = (ones xor i) & ~twos
twos = (twos xor i) & ~ones

func singleNumber(nums []int) int {
	ones, twos := 0, 0
	for i := 0; i < len(nums); i++ {
		ones = (ones ^ nums[i]) & ^twos
		twos = (twos ^ nums[i]) & ^ones
	}
	return ones
}

面试题57. 和为s的两个数字

输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

func twoSum(nums []int, target int) []int {
    if len(nums) == 0 {
		return []int{}
	}
	left, right := 0, len(nums)-1
	for left < right {
		if nums[left]+nums[right] > target {
			right--
		} else if nums[left]+nums[right] < target {
			left++
		} else {
			return []int{nums[left], nums[right]}
		}
	}
	return []int{}

}

面试题57 - II. 和为s的连续正数序列

输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
分析滑动窗口
https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/solution/shi-yao-shi-hua-dong-chuang-kou-yi-ji-ru-he-yong-h/

func findContinuousSequence(target int) [][]int {
    low := 1
	high := 2
	result := [][]int{}

	for low < high {
		temp := []int{}
		currSum := (low + high) * (high - low + 1) / 2 //当前和等于(low+high) 1+100,2+99
		if currSum == target {
			for i := low; i <= high; i++ {
				temp = append(temp, i)
			}
			result = append(result, temp)
			low++ //这里要前进
		} else if currSum < target {
			high++
		} else {
			low++
		}
	}
	return result

}

面试题59 - I. 滑动窗口的最大值

难度
简单
45
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:

滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值